WhatsApp message lifecycle webhooks
Each step in a message's lifecycle can be pushed to your endpoint as it happens. Every payload uses the WhatsApp event envelope and carries the fields every message event carries.
Delivery events
whatsapp.accepted, whatsapp.sent, whatsapp.delivered, and whatsapp.read carry only the fields every message event carries. Lifecycle events gives what each one means and when whatsapp.delivered is skipped.
Exemple de code
{
"data": {
"direction": "outbound",
"from": { "phone_number": "+13124495569" },
"metadata": { "session_id": "sess_4821" },
"tags": [{ "name": "flow", "value": "login-otp" }],
"to": { "phone_number": "+14155550100" },
"whatsapp_id": "wam_01ky7qbvswf3fvyaw3az90391c",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b"
},
"timestamp": "2026-07-23T14:51:39.913Z",
"type": "whatsapp.delivered"
}whatsapp.failed and whatsapp.rejected also carry an error object with a stable Bird code, a human-readable description, an optional meta_error_code, and occurred_at. See failure events for what separates the two. On a failure WhatsApp reported, description is WhatsApp's own explanation. A service message whose customer service window closed between accept and dispatch fails like this:
Exemple de code
{
"data": {
"direction": "outbound",
"error": {
"code": "service_window_expired",
"description": "Message failed to send because more than 24 hours have passed since the customer last replied to this number.",
"meta_error_code": "131047",
"occurred_at": "2026-07-23T14:51:40.201Z"
},
"from": { "phone_number": "+13124495569" },
"metadata": null,
"tags": null,
"to": { "phone_number": "+14155550100" },
"whatsapp_id": "wam_01ky7qbvswf3fvyaw3az90391c",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b"
},
"timestamp": "2026-07-23T14:51:40.201Z",
"type": "whatsapp.failed"
}Marking an inbound message as read records whatsapp.read in the message's timeline but does not emit a webhook.
Incoming messages
whatsapp.received carries the message's content on top of the fields every message event carries, so an endpoint can act on an inbound message without reading it back. A tap on an interactive message arrives as interactive_reply, and in_reply_to_message_id names the message it answers:
Exemple de code
{
"data": {
"direction": "inbound",
"from": {
"display_name": "Alex Rivera",
"phone_number": "+14155550100",
"username": "alexr"
},
"in_reply_to_message_id": "wam_01ky7qbvswf3fvyaw3az90391c",
"interactive_reply": {
"list": {
"description": "Next day to 2 days",
"slug": "priority_express",
"text": "Priority Mail Express"
},
"type": "list"
},
"metadata": null,
"tags": null,
"to": { "phone_number": "+13124495569" },
"whatsapp_id": "wam_01ky8b3xq4gd7pmzn2ka51f7te",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b"
},
"timestamp": "2026-07-23T14:52:04.118Z",
"type": "whatsapp.received"
}The other content arms follow the same one-of-these shape: text, image, video, audio, sticker, document, location, contact_cards, and unsupported for a kind the API does not model. GET /v1/whatsapp/messages/{message_id} documents each one.
Next steps
- Reaction webhooks: a contact's reactions arrive separately
- Receiving WhatsApp messages: every content arm and fetching inbound media
- Lifecycle events: read a message's timeline back through the API
Ressources associées
Poursuivez avec la documentation, les guides et les exemples sur ce sujet. Les ressources sont en anglais.
Regarder le guideConnecting WhatsApp to Bird: from buying a number to a live channelComprendre le conceptWhat is the 24-hour customer service window on WhatsApp?Utiliser l'outilWhatsApp message builderExplorer la fonctionnalitéWhatsApp
Essayez la pratique et obtenez un guide d'implémentation