Working with system webhooks
Learn how to receive and verify system webhook requests.
The endpoints referenced in this guide are in beta and subject to change.
A beta revision header (2024-05-15.pre) is required to use our Webhooks API. Klaviyo APIs in beta are not intended for use in production. See our versioning and deprecation policy for more information.
Before you begin
Check out our Webhooks API overview to make sure you’re ready to receive and verify webhook requests. Note that only CDP customers can manage webhooks in Klaviyo’s UI. Klaviyo app partners can access and manage webhooks via OAuth and our Webhooks API.
In this guide, we’ll detail the structure of the request that Klaviyo sends to your app when the webhook is triggered, and give an example. Make sure your app is configured to receive the request.
Webhook request structure
Request headers:
- Klaviyo-Webhook-Id
Unique ID for the webhook. - Klaviyo-Signature
The HMAC-SHA256 signature, which you can use to verify the request is coming from Klaviyo. More details on this below in HMAC signature verification. - Klaviyo-Timestamp
When the webhook request was sent.
Request body structure:
-
data
Array including webhook event payloads. Each item in this array has the following structure:- external_id
ID of the event. - payload
Payload with the same structure as a Get Event API call. - topic
The topic ID associated with this event, e.g.,event:klaviyo.email_delivered
. See Topic ID table below for available topics.
- external_id
-
meta
- klaviyo_account_id
The account ID where the event occurred. - klaviyo_webhook_id
Unique ID for the webhook. This value must match theKlaviyo-Webhook-Id
request header. If these values do not match, this indicates potential malicious activity and you should not process the webhook. - timestamp
When the webhook request was sent.
- klaviyo_account_id
Topic ID Table
You can set a system webhook to trigger off of any of the available topics shown in the table below:
Available topic IDs
Metric name | Topic ID | Description |
Bounced Email | event:klaviyo.bounced_email | When an email soft or hard bounced. |
Clicked Email | event:klaviyo.clicked_email | When a recipient clicked an email. |
Dropped Email | event:klaviyo.dropped_email | When there is a discrepancy between suppression status in Klaviyo and other upstream email service providers that Klaviyo may utilize. |
Subscribed to Email Marketing | event:klaviyo.subscribed_to_email_marketing | When a profile consents to email marketing. For example, this could be triggered via a signup form or directly adding to a list (with the consent box checked). |
Unsubscribed from Email Marketing | event:klaviyo.unsubscribed_from_email_marketing | When a user unsubscribes from email marketing. |
Manually Suppressed From Email Marketing | event:klaviyo.manually_suppressed_from_email_marketing | When a profile is manually suppressed |
Manually Unsuppressed From Email Marketing | event:klaviyo.manually_unsuppressed_from_email_marketing | When a profile is manually unsuppressed |
Email Delivered | event:klaviyo.received_email | When an email is delivered to a recipient. |
Marked Email as Spam | event:klaviyo.marked_email_as_spam | When a recipient marks an email as spam. |
Opened email | event:klaviyo.opened_email | When a recipient opens an email. |
Updated Email Preferences | event:klaviyo.updated_email_preferences | When a user clicks the “Manage Preferences” link and changes them. |
Clicked SMS | event:klaviyo.clicked_sms | When a recipient clicks on a link within an SMS message. |
Failed to Deliver Automated Response SMS | event:klaviyo.failed_to_deliver_automated_response_sms | When an automated response (e.g., from a keyword like "Help") is sent but not delivered to the recipient. |
Failed to Deliver SMS | event:klaviyo.failed_to_deliver_sms | When message is sent but not delivered to the recipient |
Received Automated Response SMS | event:klaviyo.received_automated_response_sms | When Klaviyo has recorded that a recipient received an automated SMS message in response to a keyword or the auto-responder when no keyword is recognized. |
Received SMS | event:klaviyo.received_sms | When Klaviyo has recorded that your recipient has received an SMS from a flow, campaign, or part of an SMS conversation. |
Sent SMS | event:klaviyo.sent_sms | When an inbound SMS is received by a Klaviyo sending number. |
Subscribed to SMS Marketing | event:klaviyo.subscribed_to_sms_marketing | When a profile consents to SMS marketing. |
Unsubscribed from SMS Marketing | event:klaviyo.unsubscribed_from_sms_marketing | When a profile unsubscribes from SMS marketing. |
Received Push | event:klaviyo.received_push | When Klaviyo records that your recipient has received a push notification from a flow or campaign. |
Bounced Push | event:klaviyo.bounced_push | When a push notification is sent but not delivered to the recipient. |
Opened Push | event:klaviyo.opened_push | When a recipient taps on a push notification and thus opens the app. |
Ready to Review | event:klaviyo.ready_to_review | When a product is ready to review — triggered after fulfillment or delivery and some delay. |
Submitted Rating | event:klaviyo.submitted_rating | When a product rating was submitted. This gets triggered on every review submission. |
Submitted Review | event:klaviyo.submitted_review | When a product review was submitted. This requires content to be present, in addition to a rating. |
Example webhook request
The following is an example webhook request sent from Klaviyo, triggered by the Email Delivered event.
POST ${ENDPOINT_URL}
HEADERS
Content-Type: application/json
Klaviyo-Signature: e6c00e313eaea50ca3b89a7de5a782a2014f96fb8315c065898c669d831912d1
Klaviyo-Timestamp: Thu, 04 Jan 2024 18:05:25 GMT
Klaviyo-Webhook-Id: a8b890458b4bbfaa26d961471b83c101d6de23bd826e7e5173a15310985ec3cb
BODY
{
"data": [
{
"external_id": "4L3cwQae2TX",
"payload": {
"data": {
"type": "event",
"id": "4L3cwQae2TX",
"attributes": {
"timestamp": 1700503941,
"event_properties": {
"From Number": "+18108675309",
"From Phone Region": "US",
"To Number": "+18103317156",
"To Phone Region": "US",
"Message Body": "Hello, I need help with my order!",
"Message Type": "unrecognized",
"Intent 1": "Hello",
"Intent 1 Confidence": "0.99",
"$internal": {
"vendor": 1
},
"$event_id": "d28753007bd39d4bda518f9b9eafb5ce",
"$extra": {
"Message ID": "SM5648159861e451a05bee72f3cb454549",
"Inbound Message ID": "01HFPYXW863EM519SWQDY9KTCM",
"From State": "MA",
"From City": "BOSTON",
"From Country": "US"
}
},
"datetime": "2023-11-20 18:12:21+00:00",
"uuid": "58edf080-87d0-11ee-8001-895ec29a6280"
},
"relationships": {
"profile": {
"data": {
"type": "profile",
"id": "01HFAD5MDRT48NN8VN7H1NB0BH"
},
"links": {
"self": "https://a.klaviyo.com/api/events/4L3cwQae2TX/relationships/profile/",
"related": "https://a.klaviyo.com/api/events/4L3cwQae2TX/profile/"
}
},
"metric": {
"data": {
"type": "metric",
"id": "Utt3N2"
},
"links": {
"self": "https://a.klaviyo.com/api/events/4L3cwQae2TX/relationships/metric/",
"related": "https://a.klaviyo.com/api/events/4L3cwQae2TX/metric/"
}
}
},
"links": {
"self": "https://a.klaviyo.com/api/events/4L3cwQae2TX/"
}
},
"topic": "event:klaviyo.sent_sms"
}
],
"meta": {
"klaviyo_webhook_id": "a8b890458b4bbfaa26d961471b83c101d6de23bd826e7e5173a15310985ec3cb",
"timestamp": "2024-01-04T13:05:25.891446+00:00",
"klaviyo_account_id": "abc123"
"version": "2023-06-03"
}
}
HMAC signature verification
Klaviyo generates signatures using a hash-based message authentication code (HMAC) with SHA-256. The signature is found in the Klaviyo-Signature
header.
You should generate the HMAC with SHA-256 on your end using the request body, your secret_key
, and the timestamp (Klaviyo-Timestamp
header). Then, compare this to the one sent along with the request.
We strongly recommend that you only process webhook requests that you have verified are coming from Klaviyo.
Here's some example code for checking whether a webhook request is authentic (i.e., sent from Klaviyo) by generating and comparing the HMAC signature:
import hashlib
import hmac
def is_webhook_from_klaviyo(
request,
hmac_secret # should be stored and retrieved in a secure manner
) -> bool:
signature = request.headers.get("Klaviyo-Signature")
timestamp = request.headers.get("Klaviyo-Timestamp")
message_body = request.data
computed_signature = hmac.new(hmac_secret, message_body, hashlib.sha256)
computed_signature.update(message_timestamp.encode())
return hmac.compare_digest(computed_signature.hexdigest(), message_signature)
Batching
- Events are batched for outbound webhook requests. Any single request may include up to 1,000 events in the payload, which are processed within a short window of each other.
- Events batched in the same request may come from multiple topics. Each event payload within the request has an associated
topic.
Concurrency
- Klaviyo will send up to 10 concurrent requests (if there is sufficient volume) to an endpoint URL for a given webhook subscription, i.e., per company.
- An OAuth app installed across many accounts could theoretically receive up to 10 concurrent requests per app installation from Klaviyo.
Error handling & retries
- Klaviyo is expecting your service to respond to a webhook request with a 200, 201, or 202 status code within 5 seconds. If those conditions are not met, or another connection error occurs, we will consider the webhook request failed.
- We use an exponential backoff to retry failed requests to your endpoint with a maximum delay of 1 hour.
- If the webhook subscription gets into a "failed" state, we will disable the webhook and no longer retry.
Webhook subscription disablement
One failed webhook request will not cause the webhook to become disabled. We will perform retries with backoff until we receive a successful status code (200, 201, or 202) from your endpoint URL.
If a webhook subscription remains in an "error" state for over 48 hours, the webhook will become disabled, i.e., the webhook subscription's enabled
field will be updated to false. When this occurs, we will no longer attempt to send any webhooks to your configured endpoint URL.
Once you have fixed the problems with your endpoint URL and you're ready to re-enable it, you can update your webhook subscription to do so.
If you created your webhook in the UI, you can update it there, as shown below:
If you created it via API, you can use the Update Webhook Subscription. See the example request below:
PATCH /api/webhooks/WEBHOOK_SUBSCRIPTION_ID
{
"data": {
"type": "webhook",
"id": "WEBHOOK_SUBSCRIPTION_ID",
"attributes": {
"enabled": true
}
}
}
Please note that Klaviyo will only send new events (that were processed after the webhook subscription was re-enabled) when this occurs. If you need to catch up on events that occurred while your webhook’s endpoint URL was down, you can use the Get Events endpoint and filter for the relevant
metric_id
.
Additional resources
Updated 5 months ago