HomeGuidesAPI Reference
ChangelogHelp CenterCommunityContact Us
Guides

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

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:

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 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