Working with system webhooks
Learn how to receive and verify system webhook requests.
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
. Topics are dynamic and based on the metrics available in your account. You can get a full list of the topics available for an account by using the Get Webhook Topics endpoint.
- 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
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, 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 gets into a "failed" state, we will disable the webhook and no longer retry.
Webhook 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 remains in an "error" state for over 48 hours, the webhook will become disabled, i.e., the webhook'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 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. See the example request below:
PATCH /api/webhooks/WEBHOOK_ID
{
"data": {
"type": "webhook",
"id": "WEBHOOK_ID",
"attributes": {
"enabled": true
}
}
}
Please note that Klaviyo will only send new events (that were processed after the webhook 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 about 1 month ago