occurred_at, sent_at, delivered_at, failed_at, received_at, clicked_at) are ISO-8601 UTC with millisecond precision and a Z suffix. Phone numbers (to, from) are E.164 strings. The envelope owns the event identity (id, occurred_at); the event-specific payload carries only event fields and resource references (message_id, tracking_link_id).
Identifiers
data.id uniquely identifies the event, and together with event_type it is the delivery deduplication key — if you receive the same (event_type, id) pair twice, treat the second as a redelivery. It also arrives in the X-Event-ID header.
- Message events (
message.sent,message.delivered,message.failed):idequalsmessage_id— the message is the event identity, since a message emits at most one event of each type. The sameidtherefore recurs across one message’s lifecycle (message.sentthenmessage.delivered), distinguished byevent_type. message.replied:idequalsmessage_idand identifies the inbound message (the reply itself), not the outbound message it responds to. Correlate viaconversation_id.link.clicked:idis a unique per-click identifier — every unique click delivers its own event.tracking_link_ididentifies the link and recurs across clicks of that link.
Test events
Message events includeis_test (boolean). It is true for test messages — sent from the app’s test flow or via POST /v1/projects/{id}/test — and for the synthetic event produced by the dashboard’s Send test webhook button (which additionally corresponds to no real message: its ids are random). Production traffic always carries is_test: false. link.clicked has no is_test field.
message.sent
Triggered when a message is successfully accepted by the carrier.
message.delivered
Triggered when the carrier confirms the message was delivered to the recipient’s device.
message.failed
Triggered when the carrier rejects the message or delivery fails. error_code is a short delivery-failure code (e.g. "300" opted-out, "012" invalid destination) paired with a human-readable error_message. The full list of codes, messages, and how to react to each lives in Delivery error codes. Fields describing a message that was accepted before failing (sent_at, message_text) may be absent when the message was rejected at send time.
