Events
The immutable audit trail for message lifecycle and phone state changes.
Events are immutable audit log entries. They capture what happened, when, and why - for every message lifecycle change and every phone state change - enabling debugging, analytics, and compliance reporting.
In the dashboard: a phone number's page shows its full event trail, see Phones & the Number Pool.
How they relate
Events come in two categories:
- MESSAGE events attach to a message and track its lifecycle from queuing through settlement.
- PHONE events attach to a phone. Crucially, they exist so that rejections are auditable even when no message was ever created - a duplicate, invalid number, or opted-out rejection happens before message creation, and the phone record is where that history lives.
Events are also the source for webhook fan-out: delivery attempts are recorded on the event itself, making each event the complete audit trail of both what happened and who was notified.
What fires when
During a send, events land in two places depending on how far the item got:
| Stage | Outcome | Event | Attached to |
|---|---|---|---|
| API validation | Duplicate for a communication | DUPLICATE | Phone |
| API validation | Number invalid (landline, ...) | INVALID_NUMBER | Phone |
| API validation | Phone opted out | NO_SEND_OPT_OUT | Phone |
| API accept | Message queued | QUEUED | Message |
| Send worker | AWS accepted | PROVIDER_ACCEPTED | Message |
| Send worker | AWS rejected, retries left | PROVIDER_FAILED | Message |
| Send worker | AWS rejected, not retryable or attempts exhausted | FAILED plus PROVIDER_FAILED or MAX_ATTEMPTS_REACHED | Message |
| Send worker | Preflight failure (permanent) | FAILED plus one of PHONE_NOT_FOUND, NO_SEND_OPT_OUT, MAX_ATTEMPTS_REACHED | Message |
| Send worker | Duplicate send dropped (attempt in flight) | ACTIVE_ATTEMPTS (not terminal, no settle) | Message |
| Delivery receipt | Non-failure receipt, final or interim | DELIVERED, BLOCKED, CARRIER_ROUTING, SENT | Message |
| Delivery receipt | Final failure, retries left | ATTEMPT_FAILED (message requeues, not terminal) | Message |
| Delivery receipt | Final failure, attempts exhausted | FAILED plus MAX_ATTEMPTS_REACHED (settles) | Message |
| Opt-out / opt-in | Any channel | OPTED_OUT / OPTED_IN | Phone |
FAILED is always terminal and fires exactly once per message, alongside the specific reason event that explains why. ATTEMPT_FAILED means a single delivery attempt failed but the message still has attempts remaining - it is not terminal.
Each event stores its category, type, the raw provider payload where applicable, and the time it occurred.
Key properties
Immutable. Events are never updated or deleted (webhook delivery status, recorded on the event, is the one addition made after creation).
Non-blocking. Event creation never fails the operation that triggered it - a hiccup writing an audit record won't stop a message from sending.
Queryable per entity. GET /messages/:messageId/events and GET /phones/:phone/events return the history for one message or one phone.
API reference
GET /v1/messages/:messageId/events- one message's event historyGET /v1/phones/:phone/events- one phone's event history
Calling from TypeScript? See Generating a Typed Client.