Agent skill

Kit Webhooks Skill

Receive and verify Kit (formerly ConvertKit) webhooks. Use when setting up Kit webhook endpoints, debugging X-Kit-Signature verification, or handling email marketing events like subscriber.created, subscriber.activated, subscriber.unsubscribed, and subscriber.tag_added.

Install this skill

npx skills add hookdeck/webhook-skills --skill kit-webhooks


When to Use This Skill

  • How do I receive Kit (ConvertKit) webhooks?
  • How do I verify the X-Kit-Signature header?
  • How do I handle subscriber.created, subscriber.activated, subscriber.unsubscribed, or subscriber.tag_added events?
  • Why is my Kit webhook signature verification failing, or failing only after a secret rotation?
  • Why do my legacy Kit webhooks (/v4/webhooks) have no signature?

Verification (core)

Kit signs deliveries from webhook endpoints (/v4/webhook_endpoints, or app.kit.com/webhooks) with an X-Kit-Signature header of the form t=<unix seconds>,v1=<hex>. During a secret rotation it carries one v1 per valid secret, so check every v1. v1 is the hex HMAC-SHA256 of "<t>.<raw body>", keyed with the endpoint secret. The secret starts with whsec_ but is used whole as UTF-8 key bytes: don't strip the prefix or base64-decode it, and don't use the standardwebhooks or svix libraries (this is not Standard Webhooks). Legacy /v4/webhooks deliveries are not signed.

Kit suggests rejecting timestamps older than 5 minutes, but Hookdeck retries and replays keep the original t, so the window is opt-in here (KIT_TIMESTAMP_TOLERANCE_SECONDS, default 0). Always sign over the raw body.

const crypto = require('crypto');

// toleranceSeconds = 0 (default) skips the timestamp window; a positive value opts in
function verifyKitWebhook(rawBody, header, secret, toleranceSeconds = 0) {
  if (!header) return false;
  const parts = header.split(',').map((p) => p.trim().split('='));
  const t = parts.find(([k]) => k === 't')?.[1];
  const sigs = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
  if (!t || !/^\d+$/.test(t) || sigs.length === 0) return false;
  if (toleranceSeconds > 0 && Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false; // opt-in replay window
  const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex');
  return sigs.some((sig) => {
    try { return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)); }
    catch { return false; } // length mismatch
  });
}

Kit's docs show Ruby, Node.js and Python snippets but no verification SDK, so verification is manual in every framework. In Express, read the raw body with express.raw({ type: '*/*' }) so any content type verifies.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

The body is an envelope { "delivery_id": 123456, "events": [...] } with 1 to 100 events of the same type. Iterate events and route on each event's type:

EventTriggered WhenCommon Use Cases
subscriber.createdA subscriber is added to the accountSync to CRM, welcome flows
subscriber.activatedA subscriber becomes active (confirmed)Start onboarding
subscriber.unsubscribedA subscriber unsubscribesSuppress downstream messaging
subscriber.bouncedAn address hard-bouncesClean up contact records
subscriber.complainedA subscriber reports spamSuppress and investigate
subscriber.subscribed_to_formA subscriber joins a formAttribute signup source
subscriber.tag_addedA tag is added to a subscriberSegment, grant access
subscriber.tag_removedA tag is removed from a subscriberRevoke access

More types (subscriber.added_to_sequence, subscriber.sequence_completed, subscriber.custom_field_value_updated, tag.*, custom_field.*, sequence.*, broadcast.*, post.published) are listed in references/overview.md.

Environment Variables

KIT_WEBHOOK_SECRET=whsec_xxxxx             # Returned once when the endpoint is created or its secret rotated
KIT_TIMESTAMP_TOLERANCE_SECONDS=0          # Optional replay window; a positive value opts in

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 kit --path /webhooks/kit

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →