Platform Events Reference
This section is the definitive source for all webhook event types emitted by Sent. For an end-to-end view of delivery, retries, and the full event catalogue at a glance, see the Webhooks Lifecycle page.
Event Structure
Every event that your webhook endpoint receives has a consistent envelope:
{
"field": "message",
"event": "message.delivered",
"timestamp": "2025-10-31T10:10:42Z",
"payload": {
"updated_at": "2025-10-31T10:10:41Z",
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
"template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"template_name": "order_confirmation",
"outbound_number": "+1987654321",
"agent_id": "agent_abc123",
"message_status": "DELIVERED",
"channel": "sms"
}
}{
"field": "message",
"event": "message.received",
"timestamp": "2025-10-31T10:10:42Z",
"payload": {
"message_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"updated_at": "2025-10-31T10:10:40Z",
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"inbound_number": "+1234567890",
"outbound_number": "+1987654321",
"text": "Hello, I have a question about my order",
"channel": "sms",
"received_at": "2025-10-31T10:10:40Z"
}
}{
"field": "templates",
"timestamp": "2025-10-31T12:18:14Z",
"payload": {
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"template_name": "order_confirmation",
"whatsapp_template_id": "1234567890123456",
"status": "APPROVED",
"language": "en_US",
"category": "UTILITY",
"channel": "whatsapp"
}
}| Field | Type | Description |
|---|---|---|
field | string | The parent event type: message or templates |
event | string | omitted | Granular sub-type (for example, message.delivered). Present for all message events; omitted for template events |
timestamp | string | ISO 8601 timestamp of event creation |
payload | object | Nested object containing event-specific data |
Event Types & Details
Your handler receives the full event object: field, event, timestamp, and the nested payload.
message
Triggered whenever a message's delivery status changes, or when an inbound message is received. Each status transition fires a separate event with a matching event.
Outbound message status events:
All outbound status events share one payload shape. Only event, payload.message_status, and the timestamps change between sub-types; the Status Definitions table below lists every pairing.
{
"field": "message",
"event": "message.delivered",
"timestamp": "2025-10-31T12:15:42Z",
"payload": {
"updated_at": "2025-10-31T12:15:42Z",
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
"template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"template_name": "order_confirmation",
"outbound_number": "+1987654321",
"message_status": "DELIVERED",
"channel": "sms"
}
}Messages that are held or suppressed by platform policy emit three additional sub-types with the same payload shape:
message.scheduled: the send fell inside the recipient's quiet hours and is deferred, not failed. Sent evaluates quiet-hours windows per channel against the rules for the recipient's country and time zone, both derived from the recipient's phone number. The event carriesmessage_status: "SCHEDULED". Sent releases the message automatically when the recipient's quiet hours end and it continues through the normal send pipeline, so later status events (message.sent,message.delivered, and so on) follow.SCHEDULEDis not a terminal status.message.filtered: a policy gate suppressed the send before any provider call. Either the recipient opted out or is on your phone-channel suppression list (ERR_CONSENT_BLOCKED), or your routing rules denied the send (ERR_ROUTE_DENIED). The message is not dispatched. Sent records theERR_*code internally but does not include it in the webhook payload; the send-time codes are listed in Error handling.message.blocked: an account-level precondition gated the send, such as insufficient balance, an onboarding message quota, or a template that is not approved for sending. The message is not dispatched.
Outbound Message Event Fields (all outbound sub-types):
| Field | Type | Description |
|---|---|---|
field | string | Parent event type: message |
event | string | Granular sub-type: one of message.queued, message.routed, message.sent, message.delivered, message.read, message.failed, message.scheduled, message.filtered, message.blocked |
timestamp | string | ISO 8601 timestamp of the event |
payload.updated_at | string | ISO 8601 timestamp of the status change |
payload.account_id | string | Your account UUID |
payload.message_id | string | Message UUID, used to look up the message in your DB |
payload.template_id | string | null | UUID of the template used (null if no template) |
payload.template_name | string | omitted | Name of the template used (omitted when not resolved) |
payload.outbound_number | string | Recipient phone number (E.164 format) |
payload.agent_id | string | omitted | Identifier of the agent that originated the message (omitted when not set) |
payload.message_status | string | QUEUED, ROUTED, SENT, DELIVERED, READ, FAILED, SCHEDULED, FILTERED, BLOCKED |
payload.channel | string | sms, whatsapp, or rcs |
Inbound message event (message.received):
Fires when an end-user sends a message to one of your provisioned numbers, for example, a reply to an outbound campaign, a STOP/START/HELP keyword on SMS, or a free-text reply on WhatsApp. The payload shape is different from outbound status events:
{
"field": "message",
"event": "message.received",
"timestamp": "2025-10-31T10:10:42Z",
"payload": {
"message_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"updated_at": "2025-10-31T10:10:40Z",
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"inbound_number": "+1234567890",
"outbound_number": "+1987654321",
"text": "Hello, I have a question about my order",
"channel": "sms",
"received_at": "2025-10-31T10:10:40Z"
}
}{
"field": "message",
"event": "message.received",
"timestamp": "2025-10-31T10:10:42Z",
"payload": {
"message_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"updated_at": "2025-10-31T10:10:40Z",
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"inbound_number": "+1234567890",
"outbound_number": "+1987654321",
"text": "Thanks, got your message!",
"channel": "whatsapp",
"received_at": "2025-10-31T10:10:40Z"
}
}{
"field": "message",
"event": "message.received",
"timestamp": "2025-10-31T10:10:42Z",
"payload": {
"message_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"updated_at": "2025-10-31T10:10:40Z",
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"inbound_number": "+1234567890",
"outbound_number": "+1987654321",
"text": "Tapped: View order",
"channel": "rcs",
"received_at": "2025-10-31T10:10:40Z"
}
}Inbound Message Event Fields (message.received):
| Field | Type | Description |
|---|---|---|
field | string | Parent event type: message |
event | string | Always message.received |
timestamp | string | ISO 8601 timestamp of the event |
payload.message_id | string | UUID of the inbound message record; unique per inbound message |
payload.updated_at | string | ISO 8601 timestamp of the message update |
payload.account_id | string | Your account UUID |
payload.inbound_number | string | Sender's phone number (the contact) in E.164 format |
payload.outbound_number | string | Your provisioned number that received the message |
payload.text | string | null | Message body text |
payload.channel | string | Channel the message arrived on: sms, whatsapp, or rcs |
payload.received_at | string | ISO 8601 timestamp when the provider received the message |
Status Definitions:
| Status | Sub-type | Direction | Description |
|---|---|---|---|
QUEUED | message.queued | Outbound | Message accepted and waiting to be dispatched |
ROUTED | message.routed | Outbound | Message assigned to a carrier or provider |
SENT | message.sent | Outbound | Message sent to carrier or WhatsApp |
DELIVERED | message.delivered | Outbound | Message delivered to recipient's device |
READ | message.read | Outbound | Message read by recipient (WhatsApp & RCS) |
FAILED | message.failed | Outbound | Message delivery failed permanently |
SCHEDULED | message.scheduled | Outbound | Message deferred until the recipient's quiet hours end; re-enters the send pipeline automatically when they do |
FILTERED | message.filtered | Outbound | Message suppressed by a policy gate (consent opt-out, suppression list, or routing deny); not dispatched |
BLOCKED | message.blocked | Outbound | Message gated by an account-level precondition (for example, insufficient balance); not dispatched |
RECEIVED | message.received | Inbound | Inbound message received from a contact |
templates
Triggered when a WhatsApp template moves through the approval process.
{
"field": "templates",
"timestamp": "2025-10-31T12:18:14Z",
"payload": {
"account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
"template_name": "order_confirmation",
"whatsapp_template_id": "1234567890123456",
"status": "APPROVED",
"language": "en_US",
"category": "UTILITY",
"channel": "whatsapp"
}
}Template Event Fields:
| Field | Type | Description |
|---|---|---|
field | string | Event type: templates |
timestamp | string | ISO 8601 timestamp of the event |
payload.account_id | string | Your account UUID |
payload.template_id | string | Template UUID in Sent |
payload.template_name | string | Template name |
payload.whatsapp_template_id | string | Meta's WhatsApp template ID (empty string until approved) |
payload.status | string | Template status, typically PENDING, APPROVED, REJECTED, or CATEGORY_UPDATED. Meta may also emit other lifecycle values (for example, PAUSED, DISABLED) which are forwarded verbatim. |
payload.language | string | Template language code (e.g. en_US) |
payload.category | string | Template category: MARKETING, UTILITY, AUTHENTICATION |
payload.channel | string | Channel: whatsapp |
payload.reason | string | omitted | Reason supplied by Meta (e.g. rejection reason for REJECTED, change description for CATEGORY_UPDATED). Omitted when not set. |
Event Filtering
You can configure which events to receive in the webhook settings in your Sent Dashboard to reduce noise and improve performance.
Subscribe to specific event types when creating or updating a webhook by setting event_types and, optionally, event_filters:
{
"event_types": ["message", "templates"],
"event_filters": {
"message": ["delivered", "failed", "read", "received"]
}
}The event_filters map accepts a parent event_type as the key and a list of sub-type suffixes as values. In the preceding example only message.delivered, message.failed, message.read, and message.received events are delivered. message.queued, message.routed, and message.sent are suppressed.
Available Sub-type Filters for message:
| Value | Fires for | Direction |
|---|---|---|
queued | message.queued | Outbound |
routed | message.routed | Outbound |
sent | message.sent | Outbound |
delivered | message.delivered | Outbound |
read | message.read | Outbound (WhatsApp & RCS) |
failed | message.failed | Outbound |
scheduled | message.scheduled | Outbound |
filtered | message.filtered | Outbound |
blocked | message.blocked | Outbound |
received | message.received | Inbound |
Available Filters:
- Message: Subscribe to all or specific
message.*sub-types (outbound status + inboundmessage.received) - Templates: Filter by specific template names
Legacy messages event type
messages (plural) is the retired spelling of the message parent, from before event types moved to dot-notation sub-types. Webhooks created before the rename may still store messages in their event_types array; dispatch treats it as an alias of message, so those webhooks receive all message.* events. You can't subscribe to messages when creating or updating a webhook: it is not a registered event type, and the API accepts only the canonical message.
Delivery Headers & Signing
Every outgoing webhook request includes the following headers so that you can authenticate and de-duplicate events:
| Header | Description |
|---|---|
X-Webhook-ID | UUID of the webhook configuration that produced the request |
X-Webhook-Timestamp | Unix timestamp in seconds at which the request was signed |
X-Webhook-Signature | v1,{base64_hmac}: HMAC-SHA256 over {webhook_id}.{timestamp}.{raw_body} using your signing secret |
X-Webhook-Event-Type | Fully qualified event type (message.delivered, templates, etc.) |
Signing secrets are issued in the Sent Dashboard prefixed with whsec_ (Svix-compatible). Always verify the signature server-side before trusting the payload, and compare the timestamp against your clock to reject replayed requests.
Delivery Statuses & Retries
Each webhook delivery is tracked in the Sent Dashboard with its own row and lifecycle:
| Status | Meaning |
|---|---|
PENDING | Event created, queued for delivery |
RETRYING | A previous attempt failed; the next attempt is scheduled with backoff |
DELIVERED | Endpoint returned 2xx; consecutive-failure counter resets to 0 |
FAILED | Exceeded the webhook's configured retry count (retry_count, 1–5, defaults to 3) |
Retry schedule: a delivery attempt counts as failed when your endpoint returns a non-2xx status, the request times out, or the connection fails. Failed attempts are retried with exponential backoff: the first retry fires about one minute after the failure and the delay doubles with each subsequent attempt, up to a maximum of 60 minutes between attempts. Retries stop as soon as your endpoint returns 2xx or the retry count is exhausted.
Per-event metadata stored alongside each attempt includes the delivery attempt count, HTTP status code, the first portion of the response body, the error message, and start/completion timestamps, all surfaced in the Sent Dashboard's webhook event detail view.
Auto-disable after consecutive failures
If a webhook accumulates 10 consecutive failed delivery attempts, it is automatically disabled. The counter spans events: every failed attempt increments it and any successful delivery resets it to 0. This guards against expired or compromised endpoints. Re-enable the webhook from the Sent Dashboard once your endpoint is healthy again.
This section is the canonical statement of the retry schedule, retry budget, and auto-disable threshold.
Delivery Constraints
Webhook configuration is subject to the following constraints:
- Per-webhook timeout: 5–120 seconds, defaults to 30 seconds (configured when you create or update the webhook)
- Retry count: 1–5 attempts per event, defaults to 3
- URL scheme: must be
http://orhttps:// - URL validation: private IP ranges (
127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, etc.) are rejected at creation, update, and delivery time
Webhooks Lifecycle
How a webhook delivery moves from event generation through signing, retries, and the threshold that turns a failing webhook off, plus the events Sent emits
Receiving Inbound Messages via the message.received Webhook
How to receive contact replies in real time with the message.received webhook, acknowledge and deduplicate deliveries, and skip compliance keywords.