HomeGuidesAPI Reference
ChangelogHelp CenterCommunityContact Us
Guides

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.
  • meta

    • klaviyo_account_id
      The account ID where the event occurred.
    • klaviyo_webhook_id
      Unique ID for the webhook. This value must match the Klaviyo-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.

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:

Edit webhook page in Klaviyo with a field for the endpoint URL and a reset button with a black background.

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