Agent skill
Klaviyo Webhooks Skill
Receive and verify Klaviyo webhooks. Use when setting up Klaviyo webhook handlers, debugging Klaviyo-Signature verification, or handling Klaviyo system webhook events like event:klaviyo.opened_email, event:klaviyo.clicked_email, event:klaviyo.received_sms, or event:klaviyo.unsubscribed_from_email_marketing.
Install this skill
npx skills add hookdeck/webhook-skills --skill klaviyo-webhooks
When to Use This Skill
- How do I receive Klaviyo webhooks?
- How do I verify Klaviyo webhook signatures (the
Klaviyo-Signatureheader)? - How do I handle Klaviyo system webhook events like
event:klaviyo.opened_email? - Why is my Klaviyo webhook signature verification failing?
- How do I secure a Klaviyo flow "Webhook" action that isn't signed?
Verification (core)
Klaviyo signs each system webhook with an HMAC-SHA256 over the raw request body concatenated with the Klaviyo-Timestamp header value, hex-encoded, using your endpoint secret (min 16 chars). The signature arrives in the Klaviyo-Signature header. Compute the same HMAC and compare timing-safe. There is no official SDK verification helper, so verify manually. Pass the raw body — don't JSON.parse first.
Node:
const crypto = require('crypto');
function verifyKlaviyoWebhook(rawBody, timestamp, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(rawBody) // Buffer/string of the raw HTTP body
.update(timestamp) // Klaviyo-Timestamp header value, appended
.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(signature));
} catch {
return false; // length mismatch = invalid
}
}
Python:
import hmac, hashlib
def verify_klaviyo_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
mac = hmac.new(secret.encode(), raw_body, hashlib.sha256)
mac.update(timestamp.encode()) # append Klaviyo-Timestamp
return hmac.compare_digest(mac.hexdigest(), signature)
For complete handlers with route wiring, event dispatch, and tests, see:
Unsigned flow "Webhook" action
Signature verification above applies to system webhooks (created via the Webhooks API). Klaviyo's older flow "Webhook" action sends a custom JSON payload you define and is not signed. If signing is unavailable on your account, put a hard-to-guess secret token in the endpoint URL (e.g. /webhooks/klaviyo?token=…) and reject requests that don't match. See references/verification.md.
Common Event Types (topics)
Each payload delivers a data array of events; every event carries a topic.
| Topic | Triggered When |
|---|---|
event:klaviyo.opened_email | Recipient opened an email |
event:klaviyo.clicked_email | Recipient clicked a link in an email |
event:klaviyo.bounced_email | An email bounced |
event:klaviyo.marked_email_as_spam | Recipient marked an email as spam |
event:klaviyo.unsubscribed_from_email_marketing | Profile unsubscribed from email |
event:klaviyo.received_sms | An inbound SMS was received |
event:klaviyo.sent_sms | An SMS was sent |
event:klaviyo.submitted_review | A review was submitted |
For the full topic list, see references/overview.md or call the Get Webhook Topics endpoint.
Environment Variables
KLAVIYO_WEBHOOK_SECRET=your_endpoint_secret_min_16_chars # Set when creating the webhook
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 klaviyo --path /webhooks/klaviyo
Reference Materials
- references/overview.md - Klaviyo webhook concepts, full topic list, payload structure
- references/setup.md - Create a webhook via the API, get the endpoint secret
- references/verification.md - Signature verification details and gotchas