Skip to content

Webhooks

A webhook sends events to your URL as they happen, for example when an SMS is delivered or an email bounces. See SMS events and email events for what you can subscribe to.

{
"url": "https://yourcompany.com/webhooks/connect",
"events": ["sms.delivered", "sms.failed", "sms.received", "email.bounced"]
}

The response includes the webhook’s secret, which is only shown once. Store it somewhere safe.

  • apiVersion sets the Connect-Version the payloads follow. It defaults to the current version, so your payloads don’t change when a new version is released.
  • inboundNumber limits the webhook to sms.received for one of your virtual numbers.

API reference: POST /v2/webhooks

Every webhook is a POST with a JSON body. data is the message, email, batch or suppression as it was when the event happened:

{
"id": "358120561368793088",
"type": "sms.delivered",
"occurredAt": "2026-10-10T09:30:04Z",
"apiVersion": "2026-10-01",
"workspaceId": "301245883010879488",
"data": {
"id": "357913724415131648",
"direction": "outbound",
"status": "delivered",
"...": "the rest of the message"
}
}

Each delivery has a Webhook-Id header that stays the same if we retry it. Use it to ignore a webhook you have already handled.

Every webhook is signed so you can check it came from us. The Signature-Input and Signature headers are an HTTP Message Signature (RFC 9421) using hmac-sha256, with your webhook’s secret as the key. The signature covers the method, host, path, query, and the Content-Digest, Content-Type and Webhook-Id headers. Reject a webhook whose signature doesn’t match or whose expires time has passed. Signatures expire 5 minutes after they are made.

POST /v2/webhooks/{id}/rotate-secret issues a new secret, shown once. For the next 72 hours, or until you rotate again, each webhook carries two signatures: sig under the new secret and sig-prev under the old one. The keyid of each is the secret’s version, so you can switch over without missing a webhook.

Reply with any 2xx status to tell us you received a webhook. Otherwise we retry after 5 minutes, 15 minutes, 1 hour, 4 hours, 8 hours and 12 hours. If you reply 429, we wait for your Retry-After instead.

GET /v2/webhooks/{id}/deliveries lists the deliveries to a webhook with their latest attempt, so you can see what failed and why.

If every attempt to a webhook fails for 6 hours, we switch it off and set autoDisabled. Set active back to true with PATCH /v2/webhooks/{id} once your endpoint is fixed.

  • autoDisableAfterMinutes changes how long, from 5 minutes to 7 days.
  • autoDisableExempt never switches the webhook off.
  • atRiskAlertPercent sends one warning per outage once that share of the time has passed. It defaults to 10%.

429 replies don’t count as failures.

Webhooks come from fixed IP addresses, if you need to allow them through a firewall.