Sign inGet started

É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 de livraison, de message entrant et de réaction WhatsApp 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 public 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.
L'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 dans l'ordre chronologique. Un message sortant peut s'arrêter à whatsapp.failed ou whatsapp.rejected, et son événement whatsapp.read n'apparaît que si le destinataire ouvre le message. Une chronologie entrante commence par whatsapp.received et peut enregistrer whatsapp.read après que votre espace de travail marque le message comme lu.
ÉvénementSignification
whatsapp.acceptedBird a accepté la demande d'envoi. C'est ce que 202 a signalé.
whatsapp.sentBird a transmis le message au réseau WhatsApp.
whatsapp.deliveredWhatsApp a confirmé la livraison sur l'appareil du destinataire.
whatsapp.readLe destinataire a ouvert le message.
whatsapp.failedLe message n'a pas été livré. error.code indique ce qui l'a bloqué.
whatsapp.rejectedBird a refusé le message avant de l'envoyer. Il n'a pas été facturé.
whatsapp.receivedBird a reçu un message entrant d'un contact.
Les callbacks delivered ou read applicables peuvent déclencher la part Meta du prix. Les payloads d'événement WhatsApp ne contiennent pas d'informations sur les coûts. Relisez le message avec GET /v1/whatsapp/messages/{message_id} pour voir ce qu'il a coûté. Voir Coût et facturation.
Marquer un message entrant comme lu enregistre whatsapp.read dans sa chronologie, mais n'émet pas de webhook d'accusé de lecture. Le message entrant conserve son statut received et enregistre read_at après que WhatsApp accepte l'accusé.
whatsapp.read ne change 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é. Quand le destinataire a déjà la conversation ouverte sur son appareil, Meta signale la lecture sans jamais signaler de livraison : la chronologie se lit donc whatsapp.acceptedwhatsapp.sentwhatsapp.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 arrivé restera bloqué 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.
Un callback de lecture seul peut quand même déclencher les frais Meta applicables. Bird utilise une même identité tarifaire pour les chemins de livraison et de lecture ; l'absence d'événement de livraison n'implique pas un composant Meta gratuit. Voir Coût et facturation.
La liste des types d'événements est ouverte : de nouveaux types peuvent apparaître avec le temps. Traitez donc 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 arrêté le message avant de l'envoyer à WhatsApp, et il n'a donc pas été facturé. Les causes incluent un destinataire supprimé ou désabonné, 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, dérivé du code qu'il a signalé. internal_error est l'exception : il enregistre une absence d'identifiant d'expéditeur utilisable ou un épuisement des tentatives de traitement. Une tentative de transport incertaine ne prouve pas que Meta n'a jamais reçu la requête. 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 Bird code stable, un description lisible par un humain, un meta_error_code optionnel et occurred_at. L'objet n'apparaît dans les enregistrements API et les payloads webhook que pour ces types d'événements.

Événements de réaction

Une réaction emoji annote un message existant. Elle ne crée pas de message whatsapp.received. Bird émet whatsapp.reacted quand un contact ajoute, modifie ou supprime une réaction, comme décrit dans Réactions. Les réactions envoyées par votre numéro professionnel n'émettent pas ce webhook. Voir Envoyer des réactions pour ajouter, remplacer ou supprimer votre réaction, et Recevoir des réactions pour des exemples de webhook et de REST API. Les réactions des contacts n'ouvrent pas de fenêtre de service client.
Le journal de réactions du message réagi enregistre les modifications du contact et de votre numéro professionnel : ajouts, remplacements et suppressions.
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 jusqu'à 30 jours, de sorte qu'une réaction placée sur un message plus ancien ne peut pas ê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 de la modification et un status parmi received, sent, failed ou rejected ; une entrée failed ou rejected porte la raison sur error. Chaque entrée possède un identifiant de réaction (war_…) et un horodatage occurred_at. Une suppression a emoji: null. Les modifications en attente n'ont pas d'entrée tant que leur résultat n'est pas connu. Une réaction n'est jamais facturée, donc aucun échec de réaction n'est un échec de facturation. Pour savoir ce qui est actuellement affiché sur le message plutôt que l'historique des modifications, consultez son reactions avec GET /v1/whatsapp/messages/{message_id}, qui réduit le journal à une entrée par expéditeur.

Événements de suppression

Au-delà du cycle de vie par message, un événement signale une modification de la liste de suppressions de l'espace de travail : whatsapp_suppression.created se déclenche quand une suppression s'ouvre. Le payload porte le suppression_id, le address supprimé au format E.164, le waba auquel le blocage est limité (null quand il couvre tout l'espace de travail, quel que soit le compte expéditeur), le reason et le workspace_id, pour que votre propre système puisse voir les nouveaux blocages sans interrogation périodique. Seules les ouvertures déclenchent un événement : la fin d'une suppression ne le fait pas encore, relisez donc la liste avant de considérer qu'un blocage en miroir 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 que le destinataire a déclaré lui-même est une préférence plutôt qu'une suppression, et déclenche preference.revoked à la place.

Lire les événements depuis le API

GET /v1/whatsapp/messages/{message_id}/events renvoie la chronologie dans l'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);
Un message qui a été 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, tel que ?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 quand vous ouvrez un message.
La fiche de détail de message WhatsApp dans le tableau de bord Bird, ouverte pour un message bird_order_confirmation livré : l'onglet Events affichant la chronologie du cycle de vie par message (Accepted, Sent, Delivered et Read), chacun avec son horodatage, au-dessus de la liste de messages grisée

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 l'API 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 nomme 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 structure un-parmi-ceux-ci : text, image, video, audio, sticker, document, location, contact_cards et unsupported pour un type que le API ne modélise pas. GET /v1/whatsapp/messages/{message_id} documente chacune d'entre elles.

Réactions

whatsapp.reacted se déclenche quand 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 là-bas.
whatsapp_id nomme le message auquel on a réagi, pas la réaction, et emoji est la modification effectuée par l'utilisateur. Un utilisateur qui réagit, change son emoji puis retire sa réaction produit trois événements sur ce seul message. WhatsApp n'envoie pas de suppression entre les deux premiers : une modification arrive donc comme un événement unique 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, de sorte que deux de ces trois événements peuvent partager un même timestamp. Les trier par cette valeur 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 modification 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 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 modification.
emoji est présent et null quand l'utilisateur a retiré sa réaction : un null est la suppression elle-même plutôt qu'une valeur manquante. L'emoji est livré exactement tel que WhatsApp l'a envoyé et n'est pas normalisé, de sorte que et ❤️ vous parviennent sous forme de chaînes différentes.

Étapes suivantes