Agent Skills: Klaviyo Webhooks & Events

'Implement Klaviyo webhooks with HMAC-SHA256 signature verification and

UncategorizedID: jeremylongshore/claude-code-plugins/klaviyo-webhooks-events

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/klaviyo-pack/skills/klaviyo-webhooks-events

Skill Files

Browse the full folder contents for klaviyo-webhooks-events.

Download Skill

Loading file tree…

plugins/saas-packs/klaviyo-pack/skills/klaviyo-webhooks-events/SKILL.md

Skill Metadata

Name
klaviyo-webhooks-events
Description
'Implement Klaviyo webhooks with HMAC-SHA256 signature verification and

Klaviyo Webhooks & Events

Overview

Set up Klaviyo webhooks with HMAC-SHA256 signature verification, event routing, idempotency handling, and the Webhooks API for programmatic subscription management.

This skill covers the full endpoint lifecycle in six steps: create a webhook subscription via the API, verify each request's signature, receive events in an Express handler, route them to per-topic handlers, deduplicate with Redis, and manage subscriptions. The high-level flow and the security-critical signature check live here; the complete step-by-step source is in references/implementation.md and worked scenarios are in references/examples.md.

Prerequisites

  • Klaviyo account with webhooks enabled
  • HTTPS endpoint accessible from internet
  • API key with scopes: webhooks:read, webhooks:write
  • Redis or database for idempotency (recommended)

Klaviyo Webhook Architecture

Klaviyo webhooks fire when specific topics occur in your account. Each webhook is signed with a secret key using HMAC-SHA256, sent in the webhook-signature header.

| Topic Category | Example Topics | |---------------|---------------| | Profile | profile.created, profile.updated, profile.deleted | | List | list.member.added, list.member.removed | | Segment | segment.member.added, segment.member.removed | | Campaign | campaign.sent, campaign.delivered | | Flow | flow.triggered, flow.message.sent | | Event | Custom metric events |

Instructions

Follow these six steps in order. Each is fully sourced in references/implementation.md; the security-critical signature check is inlined below because getting it wrong is the most common failure.

  1. Create a webhook subscription — call webhooksApi.createWebhook with the target endpointUrl and webhookTopics, then save the signing secret from the response as KLAVIYO_WEBHOOK_SIGNING_SECRET.
  2. Verify the signature — recompute the HMAC-SHA256 over the raw request body and compare with a timing-safe check (skeleton below).
  3. Receive events — mount an Express route with express.raw({ type: 'application/json' }) so the raw body survives for verification; reject on a bad signature, then parse.
  4. Route by topic — dispatch event.type to a per-topic handler map (profile.created, campaign.sent, ...).
  5. Deduplicate — record each processed event ID in Redis with a TTL so Klaviyo retries are short-circuited.
  6. Manage subscriptions — list, inspect topics, and delete webhooks via the API.

The signature-verification helper is the load-bearing piece — copy it exactly:

// src/klaviyo/webhook-verify.ts
import crypto from 'crypto';

export function verifyWebhookSignature(
  rawBody: Buffer | string,
  signature: string,
  secret: string
): boolean {
  if (!signature || !secret) return false;

  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(typeof rawBody === 'string' ? rawBody : rawBody.toString())
    .digest('base64');

  try {
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );
  } catch {
    return false;
  }
}

For the Express handler, event router, Redis idempotency layer, and subscription-management calls, see references/implementation.md.

Output

A working integration produces:

  • A registered webhookcreateWebhook returns a webhook ID and a signing secret; store the secret as KLAVIYO_WEBHOOK_SIGNING_SECRET.
  • HTTP responses from your endpoint200 { received: true } on success, 200 { status: 'already_processed' } on a replayed event, 401 { error: 'Invalid signature' } on a bad signature, and 500 { error: 'Processing failed' } when a handler throws.
  • Side effects per topic — e.g. a profile.created event upserts a row into your users table; a campaign.sent event emits an analytics track call.
  • Idempotency keys in Redisklaviyo:webhook:<eventId> entries with a 7-day TTL that prevent duplicate processing.

Error Handling

| Issue | Cause | Solution | |-------|-------|----------| | Invalid signature | Wrong signing secret | Verify secret matches webhook creation response | | Duplicate events | No idempotency | Track event IDs in Redis/DB | | Webhook timeout | Slow processing | Return 200 immediately, process async | | Missing events | Wrong topics subscribed | Check webhook topic subscriptions | | Body parse error | Using JSON body parser | Must use express.raw() for signature verification |

Examples

Two worked scenarios and the local-testing loop are in references/examples.md:

  • Sync new profiles into your own database — subscribe to profile.created / profile.updated and upsert each profile into your users table.
  • Track campaign sends into analytics — subscribe to campaign.sent and forward each send to your analytics pipeline, with retries short-circuited by the idempotency layer.

Minimal local-testing loop:

npm run dev              # start your app on localhost:3000
ngrok http 3000          # expose it publicly
# register the ngrok URL as the webhook endpoint in Klaviyo,
# trigger an event, and watch your logs

Resources