Événements WhatsApp
Bird enregistre des événements pour les messages WhatsApp entrants et sortants. Une chronologie sortante montre ce qui s'est passé après qu'un envoi a renvoyé 202 : acceptation, transmission à WhatsApp, livraison, lecture ou échec. Une chronologie entrante enregistre le moment où Bird a reçu le message.
L'enveloppe d'événement
Les événements publics de livraison sortante utilisent l'enveloppe webhook standard : un type, un timestamp et un objet data propre au type.
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"
}Chaque payload webhook WhatsApp publique d'un message contient whatsapp_id, workspace_id, direction, from, to, tags et metadata. whatsapp.reacted fait exception, car une réaction est une annotation sur un message et non un message à part entière ; la section Réactions ci-dessous décrit sa structure. Une adresse peut inclure un phone_number E.164, un identifiant utilisateur Meta à portée business dans bsuid, ou les deux. Un message reçu d'un utilisateur WhatsApp contient aussi le profil qu'il publie, dans username et display_name. tags et metadata valent null lorsque l'envoi n'en comportait pas. Un message envoyé en réponse porte aussi in_reply_to_message_id sur chaque événement sortant de sa chronologie, de whatsapp.accepted jusqu'à whatsapp.read, whatsapp.failed ou whatsapp.rejected, en désignant le message auquel il répond.
API des événements renvoie des enregistrements de chronologie plus légers avec un id, un type et un horodatage occurred_at. L'identifiant du message figure déjà dans l'URL de la requête.
Événements du cycle de vie
Les événements apparaissent par ordre chronologique. Un message sortant peut s'arrêter à whatsapp.failed ou whatsapp.rejected, et whatsapp.read n'apparaît que si le destinataire ouvre le message. Un message entrant comporte un seul événement de chronologie whatsapp.received.
| Événement | Signification |
|---|---|
| whatsapp.accepted | Bird a accepté la demande d'envoi. C'est ce que 202 a signalé. |
| whatsapp.sent | Bird a transmis le message au réseau WhatsApp. |
| whatsapp.delivered | WhatsApp a confirmé la livraison sur l'appareil du destinataire. |
| whatsapp.read | Le destinataire a ouvert le message. |
| whatsapp.failed | Le message n'a pas été livré. error.code indique ce qui l'a bloqué. |
| whatsapp.rejected | Bird a refusé le message avant de l'envoyer. Il n'a pas été facturé. |
| whatsapp.received | Bird a reçu un message entrant d'un contact. |
whatsapp.delivered est aussi le moment où la part Meta du prix du message est facturée, même si l'événement ne l'indique pas : les payloads d'événements WhatsApp ne contiennent aucun coût. Relisez le message avec GET /v1/whatsapp/messages/{message_id} pour voir ce qu'il a coûté. Voir Coût et facturation.
whatsapp.read ne modifie pas le status du message. Un message livré reste delivered ; le message enregistre aussi la lecture dans read_at.
whatsapp.delivered peut être entièrement sauté. Lorsque le destinataire a déjà la conversation ouverte sur son appareil, Meta signale la lecture sans jamais signaler de livraison, si bien que la chronologie se lit whatsapp.accepted → whatsapp.sent → whatsapp.read sans whatsapp.delivered entre les deux. Traitez read comme une preuve de livraison : un consommateur qui attend delivered avant de considérer le message comme arrivé se bloquera précisément sur les destinataires qui l'ont vu le plus vite, et un consommateur qui calcule un taux de livraison à partir de delivered seul le sous-estime. Le status du message reste sent dans ce cas, car seul un accusé de livraison le fait avancer.
Le saut a aussi une conséquence de facturation : la part Meta du prix est facturée sur l'accusé delivered, si bien qu'un message lu de cette façon ne porte pas de passthrough_amount. Voir Coût et facturation.
La liste des types d'événements est ouverte : de nouveaux types peuvent être ajoutés au fil du temps, donc traitez une valeur non reconnue comme un événement futur plutôt que comme une erreur.
Événements d'échec
whatsapp.failed et whatsapp.rejected sont terminaux. Un rejet signifie que Bird a bloqué le message avant de l'envoyer à WhatsApp, donc il n'a pas été facturé. Les causes possibles incluent un destinataire supprimé ou désinscrit, un solde de portefeuille insuffisant ou une destination sans prix configuré. Un échec signifie que le message n'a pas été livré, et error.code indique qui en a décidé. La plupart des codes portent le verdict de WhatsApp, traduit depuis le code qu'il a signalé. internal_error fait exception : il signifie que le message n'a jamais atteint WhatsApp, soit parce que le numéro d'envoi ne disposait d'aucune accréditation utilisable, soit parce que Bird a réessayé l'envoi jusqu'à abandonner. meta_error_code contient le code de WhatsApp lorsqu'il est disponible, et un échec internal_error n'en comporte aucun par construction.
Les deux événements contiennent un objet error avec un code Bird stable, un description lisible par un humain, un meta_error_code optionnel et occurred_at. L'objet apparaît dans les enregistrements API et les payloads webhook uniquement pour ces types d'événements.
Événements de réaction
Une réaction emoji est une annotation sur un message et non un message à part entière, elle n'apparaît donc sur aucune des deux chronologies. Une réaction ne crée aucun événement whatsapp.*, une réaction entrante ne déclenche aucun webhook whatsapp.received, et aucun webhook ne contient de réaction. Chaque changement est enregistré dans le journal de réactions du message concerné : chaque emoji placé, chaque emoji remplacé par un autre et chaque emoji retiré, par le contact comme par votre numéro professionnel.
Un cas n'est enregistré nulle part. Bird associe une réaction à son message via un identifiant fournisseur qu'il conserve pendant 15 jours, tandis que WhatsApp accepte une réaction sur un message vieux de 30 jours au maximum, si bien qu'une réaction placée sur un message plus ancien ne peut être associée et n'atteint ni le journal ni reactions. Un message sans entrées n'est donc pas la preuve que personne n'y a réagi.
Consultez ce journal avec GET /v1/whatsapp/messages/{message_id}/reaction-events, du plus récent au plus ancien. Une entrée indique l'emoji, l'auteur du changement et un status parmi received, sent, failed ou rejected ; une entrée failed ou rejected porte la raison dans error. Une réaction n'est jamais facturée, donc aucun échec de réaction n'est un échec de facturation. Pour voir ce qui est actuellement en place sur le message plutôt que l'historique des changements, lisez ses reactions avec GET /v1/whatsapp/messages/{message_id}, qui réduit le journal à une seule entrée par expéditeur.
Événements de suppression
Au-delà du cycle de vie par message, un événement signale un changement dans la liste de suppression de l'espace de travail : whatsapp_suppression.created se déclenche à l'ouverture d'une suppression. La payload contient le suppression_id, le address supprimé au format E.164, le waba auquel le blocage est limité (null lorsqu'il couvre l'ensemble de l'espace de travail, quel que soit le compte expéditeur), le reason et le workspace_id, afin que votre propre système puisse détecter les nouveaux blocages sans interrogation périodique. Seules les ouvertures déclenchent un événement : la fin d'une suppression n'en déclenche pas encore, donc relisez la liste avant de considérer qu'un blocage répliqué est toujours en vigueur :
Exemple de code
{
"type": "whatsapp_suppression.created",
"timestamp": "2026-08-25T14:52:03.192524705Z",
"data": {
"suppression_id": "was_01krdgeqcxet5s7t44vh8rt9mg",
"address": "+14155550100",
"waba": null,
"reason": "manual",
"workspace_id": "ws_01ky7m21hjffh9s8kq76gyb1xf"
}
}Un désabonnement exprimé par le destinataire lui-même est une préférence plutôt qu'une suppression, et déclenche preference.revoked à la place.
Lecture des événements depuis API
GET /v1/whatsapp/messages/{message_id}/events renvoie la chronologie par ordre chronologique. La liste bornée n'est pas paginée. La lecture des événements nécessite une clé API avec whatsapp:read :
const { data } = await bird.whatsapp.listEvents("wa_abc123");
for (const event of data) console.log(event.type, event.occurred_at);events = client.whatsapp.list_events("wa_abc123")
for event in events.data:
print(event.type, event.occurred_at)events, err := client.Whatsapp.ListEvents(context.Background(), "wam_01krdgeqcxet5s7t44vh8rt9mg", bird.WhatsappListEventsParams{})
if err != nil {
log.Fatal(err)
}
for _, e := range events.Data {
fmt.Println(e.Id, e.Type)
}$events = $bird->whatsapp->listEvents('wamid_01krdgeqcxet5s7t44vh8rt9mg');
foreach ($events->getData() ?? [] as $event) {
echo $event->getType(), ' ', $event->getId(), "\n";
}bird whatsapp list-events <message-id>{
"name": "whatsapp_list_events",
"arguments": {
"message_id": "<message-id>"
}
}curl https://us1.platform.bird.com/v1/whatsapp/messages/wam_.../events \
-H "Authorization: Bearer $BIRD_API_KEY"Un message accepté, envoyé, livré et lu renvoie quatre événements :
Exemple de code
{
"data": [
{
"id": "ev_01ky7q6a1fejfbvs0myn41hj41",
"occurred_at": "2026-07-23T14:48:34.71Z",
"type": "whatsapp.accepted"
},
{
"id": "ev_01ky7q6a2denvtd6jg1vqwmg13",
"occurred_at": "2026-07-23T14:48:35.671Z",
"type": "whatsapp.sent"
},
{
"id": "ev_01ky7q6a2zff9r2qm74mmg1g6z",
"occurred_at": "2026-07-23T14:48:36.642Z",
"type": "whatsapp.delivered"
},
{
"id": "ev_01ky7q6c21frssf0vj8h50qysw",
"occurred_at": "2026-07-23T14:48:38.65Z",
"type": "whatsapp.read"
}
]
}Passez type pour obtenir un seul type d'événement public exact, par exemple ?type=whatsapp.failed ou ?type=whatsapp.read. Omettez-le pour la chronologie complète.
Cette même chronologie est ce que la page du journal WhatsApp affiche lorsque vous ouvrez un message.

Webhooks
Abonnez-vous à whatsapp.accepted, whatsapp.sent, whatsapp.delivered, whatsapp.read, whatsapp.failed, whatsapp.rejected, whatsapp.received et whatsapp.reacted depuis la page Webhooks ou API des webhooks. Le guide Webhooks couvre les endpoints, les signatures et les réessais.
whatsapp.received porte le contenu du message en plus de l'enveloppe ci-dessus, de sorte qu'un endpoint peut agir sur un message entrant sans le relire. Un appui sur un message interactif arrive sous forme de interactive_reply, et in_reply_to_message_id désigne le message auquel il répond :
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"
}Les autres branches de contenu suivent la même forme l'un-parmi-ceux-ci : text, image, video, audio, sticker, document, location, contact_cards et unsupported pour un type que API ne modélise pas. GET /v1/whatsapp/messages/{message_id} documente chacune d'elles.
Réactions
whatsapp.reacted se déclenche lorsqu'un utilisateur WhatsApp réagit à l'un de vos messages. C'est le seul événement WhatsApp qui ne fait pas partie de la chronologie de livraison d'un message : il n'apparaît pas dans GET /v1/whatsapp/messages/{message_id}/events, et il n'y a rien pour le filtrer à cet endroit.
whatsapp_id désigne le message auquel la réaction s'applique, et non la réaction elle-même, et emoji est le changement effectué par l'utilisateur. Un utilisateur qui réagit, change son emoji puis retire sa réaction produit trois événements sur ce même message. WhatsApp n'envoie pas de suppression entre les deux premiers, si bien qu'un changement arrive sous forme d'un seul événement portant le nouvel emoji.
Exemple de code
{
"data": {
"emoji": "👍",
"from": { "phone_number": "+14155550100" },
"to": { "phone_number": "+13124495569" },
"whatsapp_id": "wam_01ky8b3xq4gd7pmzn2ka51f7te",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b"
},
"timestamp": "2026-07-23T14:52:11.000Z",
"type": "whatsapp.reacted"
}WhatsApp indique l'heure de la réaction à la seconde près, si bien que deux de ces trois événements peuvent partager un même timestamp. Les trier par cet horodatage ne les ordonnera pas, pas plus que l'ordre de livraison, que les réessais rendent peu fiable. Agissez sur la réaction que chaque événement porte, en tant que changement qu'il décrit. Ne reconstituez pas la séquence à partir des événements et ne traitez pas le dernier arrivé comme la réaction en vigueur sur le message, car ni les horodatages ni l'ordre d'arrivée ne le permettent. Relisez le message pour voir les réactions en vigueur : GET /v1/whatsapp/messages/{message_id} renvoie une entrée par expéditeur dans reactions, et le journal de réactions du message contient chaque changement.
emoji est présent et vaut null lorsque l'utilisateur a retiré sa réaction, si bien qu'un null est le retrait lui-même et non une valeur manquante. L'emoji est livré exactement tel que WhatsApp l'a envoyé, sans normalisation, si bien que ❤ et ❤️ vous parviennent sous forme de chaînes différentes.
Étapes suivantes
- Journal WhatsApp : la vue par message qui affiche cette chronologie
- Envoyer des messages WhatsApp : là où commence le cycle de vie d'un message
- Guide Webhooks : endpoints, signatures, réessais et catalogue complet des événements
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