SMS events
Every message moves through a lifecycle, and Bird emits an event at each step. This page is the full event vocabulary; how events are delivered to your endpoint (signatures, retries, replay) is covered in the Webhooks guide.
The delivery lifecycle, as a path through the event types:
- sms.accepted: Bird has the message and is preparing to hand it to a carrier.
- sms.sent: Bird handed the message to the carrier and is awaiting a delivery receipt.
- One terminal event:
- sms.delivered: The carrier confirmed delivery to the handset.
- sms.undelivered: The carrier reported a temporary non-delivery, such as an unavailable handset.
- sms.failed: A permanent failure stopped delivery.
- sms.expired: The message's validity window elapsed before a terminal receipt arrived.
The terminal events surface the carrier's delivery receipt, what SMS platforms call a delivery report or DLR.
The exception is sms.rejected: the message was refused (by a policy check, a charge that could not complete, or a carrier that turned it away) rather than attempted and lost. A message rejected during processing carries sms.rejected as its only event.
Bird also receives replies. When a subscriber texts one of your numbers, Bird stores the message and emits sms.received, so you can act without polling. The payload carries the body, segment breakdown, both numbers, and the operator when the carrier reports one.
Bird evaluates the reply against the keyword rules for that number. A supported stop keyword such as STOP records a sender-and-subscriber suppression and still emits sms.received.
The event type is an open enum: Bird may add new event types over time, so treat an unrecognized type as a future event rather than an error. Match the types you handle and ignore the rest.
The event envelope
Events arrive at your webhook endpoint in the Standard Webhooks nested envelope described in the Webhooks guide: three fields, type, timestamp, and a type-specific data object. The event's identity is not in the body: it rides in the webhook-id HTTP header, which is stable across retries of the same delivery and is your deduplication key.
| Field | Description |
|---|---|
| type | One of the event types on this page, such as sms.delivered |
| timestamp | When the event occurred (RFC 3339); sort by this, never by arrival order, since deliveries are not ordered |
| data | Event-specific payload |
Every SMS event's data contains sms_id, workspace_id, and the to and from addresses. It also echoes the send's tags and metadata so you can route and correlate events without another lookup. Each is null when the send carried none.
Code example
{
"type": "sms.delivered",
"timestamp": "2026-06-10T14:30:00Z",
"data": {
"sms_id": "sms_01krdgeqcxet5s7t44vh8rt9mg",
"workspace_id": "ws_01krdgeqcxet5s7t44vh8rt9mg",
"to": "+15551234567",
"from": "Bird",
"carrier": "Example Wireless",
"mcc_mnc": "310260",
"tags": [{ "name": "campaign", "value": "spring-2026" }],
"metadata": { "order_id": "ord_123" }
}
}Lifecycle events
sms.accepted
Fires when Bird accepts the send and begins preparing to hand it to a carrier. Payload: the identity base only.
sms.sent
Fires when Bird has handed the message to the carrier and is awaiting a delivery receipt. Payload adds carrier and mcc_mnc (the handling network and its mobile country/network code), each null when unknown. To measure processing latency, compare this event's timestamp with sms.accepted's.
sms.delivered
The carrier confirmed the message reached the handset. Payload adds carrier and mcc_mnc, each null when the receipt didn't identify them.
Failure events
Each failure event's payload adds an error object: a Bird-stable code (for example unreachable or blocked_by_carrier), a human-readable description, the raw carrier_error_code when one was supplied, and occurred_at.
sms.undelivered
A non-permanent non-delivery: the handset was off or unreachable.
sms.failed
A permanent delivery failure stopped the message.
sms.rejected
The message was refused by Bird's checks during processing, a charge that could not complete, or a carrier that turned it away. A rejection stops the message before a delivery attempt succeeds. An exhausted wallet ends here with the error code insufficient_balance, and a message whose charge could not complete is not billed.
sms.expired
The message's validity window elapsed before a terminal delivery receipt arrived. The error describes why it was still undelivered when the window closed, usually unreachable: the handset stayed off or out of coverage for the whole window.
Reading a message's timeline
Webhooks deliver events to your systems. For a one-off review, the SMS log renders the same stream as a timeline with timestamps, carrier details, and errors. To retrieve the timeline programmatically, call GET /v1/sms/messages/{message_id}/events. To read only the latest state, call GET /v1/sms/messages/{message_id}.
Next steps
- Webhooks & events: set up an endpoint, verify signatures, and handle retries and replay.
- SMS log: inspect the per-message timeline these events drive.
- Sending SMS: set the tags and metadata echoed on every event.