Sign inGet Started

Envoyer des messages WhatsApp

Ce guide présente le point de terminaison d'envoi, POST /v1/whatsapp/messages. Vous construisez une charge utile JSON avec un destinataire et exactement un type de contenu : un modèle préapprouvé, ou un message de service contenant du texte, une image, une vidéo, un audio, un sticker, un document, une position, des fiches de contact ou un élément sur lequel appuyer. Bird renvoie 202 Accepted avec un identifiant de message et effectue la livraison de manière asynchrone. Le type que vous pouvez envoyer dépend de la fenêtre de service client. Chaque requête envoie un message à un seul destinataire, et il n'existe aucun point de terminaison d'envoi groupé.

Un envoi minimal

La plus petite charge utile valide se compose d'un destinataire to et d'un template avec son slug. Ajoutez language si vous voulez une langue précise ; sans ce champ, le modèle est envoyé dans sa langue par défaut. Renseignez les variables déclarées par le modèle via parameters.

L'appel curl utilise l'hôte américain ; si votre clé commence par bk_eu1_, appelez plutôt https://eu1.platform.bird.com. Les SDK déduisent la région de votre clé et ne définissent donc aucun hôte.

const msg = await bird.whatsapp.send({
  to: "+15551234567",
  template: {
    slug: "bird_otp",
    parameters: { code: "123456" },
  },
});
console.log(msg.id, msg.status);

La fenêtre de service client

Le type de message que vous pouvez envoyer dépend d'un seul état : la fenêtre de service client est-elle ouverte ?

Le contact ouvre la fenêtre en envoyant un message ou en appelant votre numéro professionnel. Elle reste ouverte 24 heures et repart de zéro à chaque nouveau message de sa part. Tant qu'elle est ouverte, vous pouvez envoyer un message de service, c'est-à-dire n'importe quel contenu libre : texte, image, vidéo, audio, sticker, document, position, fiches de contact ou contenu interactif. Une fois qu'elle a expiré, seul un modèle préapprouvé lui parvient, et sa réponse à ce modèle rouvre la fenêtre.

Bird suit la fenêtre pour vous : un message de service envoyé dans une fenêtre fermée est refusé avant toute création ou facturation, et la requête renvoie une erreur 422 E15044 WhatsAppServiceWindowClosed. Cette vérification est faite au mieux et laisse passer en cas de doute. Un 202 ne prouve donc pas que la fenêtre était réellement ouverte lors de l'expédition. Si la fenêtre expire entre l'acceptation et l'expédition, l'échec survient de manière asynchrone, avec service_window_expired dans le champ last_error du message.

Consultez la fenêtre de service client pour le cycle de vie complet : ce qui l'ouvre, ce qui la réinitialise et son lien avec la tarification.

Construire la charge utile

Destinataire

to désigne une seule destination, sous la forme d'un numéro de téléphone, d'un identifiant utilisateur propre à l'entreprise ou d'un identifiant de groupe. Un numéro de téléphone est au format E.164 : un + initial, l'indicatif du pays, puis le numéro d'abonné, par exemple +14155550100. Nous validons le numéro : une valeur qui ne peut pas être un numéro réel et joignable (longueur incorrecte, préfixe non attribué) est rejetée avec une 422 WhatsAppInvalidRecipient avant toute facturation. Il n'existe ni tableau de destinataires ni envoi groupé : pour joindre plusieurs personnes qui ne font pas partie d'un même groupe, vous effectuez un appel par destinataire.

Un identifiant utilisateur propre à l'entreprise, par exemple US.13491208655302741918, permet d'écrire à un contact dont vous n'avez pas le numéro de téléphone. C'est ainsi que vous répondez à un contact qui vous a joint sans numéro. Deux choses changent : le numéro d'envoi doit appartenir au même portefeuille d'entreprise que celui auquel l'identifiant est rattaché, et un modèle de code à usage unique exige un numéro de téléphone. Un modèle géré par Bird est refusé dès l'acceptation avec une 422 WhatsAppRecipientNotSupportedForTemplate ; un modèle d'authentification créé par votre espace de travail est accepté puis échoue, car Meta exige un numéro de téléphone pour ce type de modèle.

to accepte une autre forme : un identifiant de groupe WhatsApp, par exemple wag_01krdgeqcxet5s7t44vh8rt9mg, qui envoie le message à tous les participants de cette discussion de groupe. Un envoi de groupe omet from et rend compte de la distribution pour l'ensemble du groupe plutôt que pour un seul destinataire. C'est pourquoi Envoyer à un groupe WhatsApp le traite sur une page dédiée.

Modèle

template désigne le modèle préapprouvé à envoyer :

  • slug (obligatoire) : l'identifiant court du modèle, par exemple bird_order_confirmation. Il doit correspondre à un modèle de votre catalogue (lettres minuscules, chiffres et traits de soulignement).
  • language : la balise de langue du modèle, par exemple en ou pt-BR. Omettez-la pour envoyer la langue par défaut du modèle. Si vous indiquez une langue que le modèle ne possède pas, la requête renvoie une 422 qui liste les langues disponibles. Le message accepté renvoie la langue retenue.
  • parameters : les valeurs des variables du modèle, sous la forme d'un objet unique indexé par nom (voir Composants et paramètres). Omettez-le pour un modèle sans variables.
  • components : les mêmes valeurs au format de composants de Meta, à la place de parameters. Quelques modèles n'acceptent que cette forme.

Consultez vos modèles, leurs langues et un aperçu de chacun sur la page Templates.

Composants et paramètres

Les modèles contiennent des variables, nommées ({{ref}}, {{amount}}) ou numérotées ({{1}}, {{2}}). Renseignez-les dans parameters, un seul objet. Le champ variables de la version du modèle liste les clés qu'il accepte et indique celles qui sont facultatives. Chaque clé correspond à l'un des cas suivants :

  • le nom d'une variable nommée, par exemple order_id
  • le numéro d'une variable numérotée, par exemple 1
  • header_url, header_filename ou header_location pour un en-tête média ou de localisation, et header_url_1, header_url_2, etc. pour l'en-tête d'une carte de carrousel
  • button_url pour le suffixe numéroté d'un bouton URL, et button_url_1, button_url_2, etc. pour le bouton d'une carte de carrousel. Un suffixe nommé utilise son propre nom.
  • code pour le code d'un modèle d'authentification et pour la valeur d'un bouton de copie du code
  • une entrée lue par les liaisons de paramètres de la langue

Chaque valeur est une chaîne, un nombre ou un booléen. header_location accepte un objet avec latitude et longitude. Si vous omettez un en-tête média, l'exemple approuvé du modèle est envoyé. Un modèle importé depuis Meta n'a pas d'exemple : il nécessite donc la clé de l'en-tête média. Les clés que la langue n'utilise pas sont ignorées.

Exemple de code
{
  "to": "+15551234567",
  "from": "+13124495648",
  "template": {
    "slug": "order_update",
    "language": "en",
    "parameters": { "order_id": "A-4192", "first_name": "Ava" }
  }
}

Avec le CLI, transmettez l'objet avec --parameters :

Exemple de code
bird whatsapp send --to +15551234567 --from +13124495648 \
  --template order_update --language en \
  --parameters '{"order_id":"A-4192","first_name":"Ava"}'

Un modèle d'authentification dont le corps indique {{1}} is your verification code et dont le bouton copie le code reçoit le code une seule fois, sous la forme code. Bird l'insère à la fois dans le corps et dans le bouton :

Exemple de code
{
  "parameters": { "code": "481920" }
}

Une langue dont les liaisons lisent des données de contact, comme bird.contact.first_name, ne peut pas être envoyée en message individuel et renvoie E15091. Envoyez-la plutôt dans une diffusion, où le contact de chaque destinataire fournit les valeurs. Une diffusion ignore un destinataire dont les données de contact laissent une valeur obligatoire vide. Envoyez parameters ou components, mais pas les deux. Certains modèles n'acceptent que parameters : ceux dont la langue calcule ses valeurs avec des liaisons de paramètres, et ceux qui ont des variables nommées dans une carte de carrousel ou dans l'URL d'un bouton.

Certains modèles n’acceptent que components, car une seule clé ne permet pas de distinguer leurs emplacements. C’est le cas lorsqu’un modèle numérote {{1}} dans plusieurs blocs, par exemple l’en-tête et le corps. C’est aussi le cas lorsqu’un bloc contient deux boutons URL avec des suffixes numérotés, ou une variable {{button_url}} à côté d’un bouton URL numéroté. L’envoi d’un tel modèle avec parameters renvoie 422.

Remplir un modèle par composant. components est l’alternative à parameters, et la seule forme possible pour un modèle qu’une clé unique ne peut pas adresser. Chaque composant indique un type (header, body, button ou carousel) et un tableau parameters. Un carousel prend à la place un tableau cards, avec une entrée par carte approuvée, dans l’ordre d’approbation. Chaque carte contient son propre tableau components d’entrées header, body et button. Chaque paramètre indique son propre type (text, image, video, gif, document ou location) et contient le champ correspondant : pour text, une simple chaîne ; pour image/video/gif/document, une url https publique ; pour location, un point sur la carte. Un modèle à paramètres nommés exige un name sur chaque paramètre, correspondant exactement aux noms déclarés par le modèle (voir Référence des champs). Un modèle positionnel omet name et reçoit ses valeurs dans l’ordre de {{n}} : le premier paramètre remplit donc {{1}}. Dans les deux cas, des paramètres qui ne correspondent pas aux déclarations du modèle renvoient une WhatsAppTemplateParameterMismatch 422. Sur un modèle géré par Bird, un composant header est ignoré, car aucun modèle géré par Bird ne déclare de variable d’en-tête ; sur un modèle créé par votre espace de travail, il est transmis, et c’est ainsi qu’un modèle utilitaire ou marketing avec en-tête média reçoit son image.

Le même modèle de code de vérification reçoit le code à la fois comme paramètre du corps et comme paramètre du bouton, de manière positionnelle (sans name) :

Exemple de code
{
  "components": [
    { "type": "body", "parameters": [{ "type": "text", "text": "481920" }] },
    { "type": "button", "parameters": [{ "type": "text", "text": "481920" }] }
  ]
}

Catégorie et expéditeur

La catégorie d’un modèle (authentication, utility ou marketing) détermine la manière dont WhatsApp traite le message et, avec le pays de destination, son coût.

Le propriétaire de l’expéditeur détermine si vous devez l’indiquer :

  • Un modèle géré par Bird (son slug commence par bird_) est envoyé depuis le numéro que Bird réserve à cette catégorie : omettez donc from. Si vous le renseignez, la requête renvoie une WhatsAppSenderNotAllowed 422.
  • Tout autre envoi indique son propre expéditeur dans from : un message de service de n’importe quel type, et tout modèle créé par votre espace de travail. Le numéro doit appartenir à votre espace de travail. Si vous l’omettez, la requête renvoie une WhatsAppSenderRequired 422, et un numéro depuis lequel l’espace de travail ne peut pas envoyer renvoie une WhatsAppSenderNotFound 422. Un modèle créé par votre espace de travail doit aussi se trouver sur le même compte WhatsApp Business que le numéro ; sinon, l’envoi renvoie une WhatsAppSenderWABAMismatch 422.

La page Configuration des numéros de téléphone présente les deux types de numéros et explique comment connecter votre propre numéro.

Messages de service

Au lieu de template, incluez exactement un des champs suivants : text, image, video, audio, sticker, document, location, contact_cards ou interactive. Ces neuf types sont tous des messages de service : ils nécessitent donc une fenêtre de service client ouverte. Chacun exige aussi from, un numéro appartenant à votre espace de travail ; les numéros gérés par Bird ne peuvent pas les envoyer.

  • text : { "body": "..." }, jusqu’à 4 096 caractères. Ajoutez "preview_url": true pour afficher un aperçu du premier lien présent dans body.
  • image, video, audio, sticker, document : chacun prend une URL https publique que WhatsApp récupère au moment de l'envoi (url) ; une URL signée doit donc rester valide au-delà de l'envoi. Une URL http est rejetée d'emblée. WhatsApp récupère lui-même le fichier : une URL inaccessible, une URL qui sert un type non pris en charge ou un fichier qui dépasse la taille maximale de son type est donc accepté, puis échoue, avec media_rejected dans le last_error du message et la raison propre à WhatsApp dans description. image, video et document acceptent aussi un caption facultatif ; document accepte aussi un filename facultatif ; audio accepte un indicateur voice facultatif pour un affichage sous forme de note vocale.
  • location : { "latitude": ..., "longitude": ... } (tous deux obligatoires, en degrés décimaux), plus name et address facultatifs.
  • contact_cards : un tableau de cinq contacts au maximum, partagés dans un seul message. Le name de chaque fiche exige formatted_name et au moins un autre élément (first_name, last_name, middle_name, prefix ou suffix) ; phone_numbers, emails, urls et addresses acceptent chacun dix entrées au maximum, et org et birthday (sous la forme YYYY-MM-DD) sont facultatifs. Un phone_number au format E.164 ajoute à la fiche un bouton qui ouvre une conversation avec ce numéro.
  • interactive : un texte de corps et un élément sur lequel appuyer, parmi six types : boutons de réponse, menu de liste, bouton de lien, carrousel de médias, ou bouton unique demandant au destinataire sa position ou son numéro de téléphone. Messages interactifs décrit le format de chaque type, les réponses produites par un appui et les limites.
Exemple de code
{
  "to": "+16505551234",
  "from": "+13124495648",
  "text": { "body": "Your order shipped: https://example.com/track/A1B2C3", "preview_url": true }
}

Une requête sans contenu, ou avec plus d'un type de contenu, est rejetée avec une 422.

Citer un message

Définissez in_reply_to_message_id sur l'ID d'un message WhatsApp pour envoyer votre message en réponse à celui-ci, comme lorsque vous appuyez sur Répondre dans l'application WhatsApp pour citer un message. Le destinataire voit votre message avec le message cité au-dessus, et le champ est renvoyé à chaque lecture du message.

Cela fonctionne aussi dans l'autre sens : un message entrant que WhatsApp marque comme réponse contient l'ID du message cité dans ce même champ, ce qui vous permet de savoir à quel message une réponse répond. Un message entrant que WhatsApp ne marque pas ne contient aucun ID, et la résolution peut aussi échouer. Pour une corrélation fiable, utilisez des identifiants de réponse interactive explicites, associés à l'état de conversation ou de tâche stocké par votre application. Les metadata sortants restent sur l'enregistrement sortant et ne sont pas copiés automatiquement sur la réponse.

La citation est résolue avant l'acceptation de l'envoi : une citation impossible à afficher fait donc échouer la requête elle-même, et rien n'est créé ni facturé. Un ID qui ne désigne aucun message de cet espace de travail, ou un message de plus de 15 jours, durée pendant laquelle un message peut être cité, renvoie une 404 E15071 WhatsAppReferencedMessageNotFound. Un ID désignant un message qui n'a jamais atteint WhatsApp, ou un message d'une autre conversation que celle des to et from de cet envoi, renvoie une 422 E15072 WhatsAppMessageNotQuotable. Si Bird ne peut pas joindre le stockage qui fournit la réponse, l'envoi renvoie une 503 E15073 WhatsAppMessageLookupUnavailable, et une nouvelle tentative est alors utile. La citation fonctionne aussi bien pour un envoi de modèle que pour un envoi libre.

Exemple de code
{
  "to": "+16505551234",
  "from": "+13124495648",
  "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
  "text": { "body": "Yes, that slot is still free." }
}

Tags et métadonnées

Trois champs facultatifs associent votre propre contexte à un message ; tous trois sont renvoyés lors des lectures API et accompagnent chaque événement webhook du message :

  • tags : jusqu'à 20 libellés { "name": ..., "value": ... } structurés, pour les dimensions à faible cardinalité selon lesquelles vous filtrez et établissez vos rapports (une campagne, une variante de test). Les noms et les valeurs acceptent les lettres ASCII, les chiffres, le trait de soulignement et le trait d'union ; les noms sont limités à 32 caractères et uniques au sein d'un envoi, les valeurs à 64. Filtrez la liste des messages par tag (?tag=campaign ou ?tag=campaign:launch-week) ; la page Metrics ventile la remise par tag.
  • metadata : un objet JSON libre, jusqu'à 2 Ko une fois sérialisé, pour le contexte propre à chaque envoi dont vous n'avez pas besoin comme dimension de filtrage (un identifiant de commande interne, une référence de session).
  • reference : une chaîne de 1 à 255 caractères qui vous permet de retrouver le message, comme un numéro de commande ou de ticket. ?reference= sur la liste des messages la compare exactement, en respectant la casse, et plusieurs messages peuvent partager la même valeur. Définissez-la uniquement si vous devez effectuer des recherches dessus, et placez toute autre donnée dans metadata.
Exemple de code
{
  "tags": [{ "name": "campaign", "value": "order-confirmations" }],
  "metadata": { "order_id": "ord_8271" },
  "reference": "ord_8271"
}

Référence des champs

ChampTypeObligatoireLimites / remarques
tochaîneouiUn destinataire par message : un numéro de téléphone E.164, un identifiant utilisateur propre à l'entreprise, qu'aucun modèle de code de vérification à usage unique n'accepte, ou un identifiant de groupe WhatsApp (wag_…), qui envoie le message à tous les participants de ce groupe
fromchaîne (E.164)non**À omettre pour un modèle géré par Bird, qui choisit son propre expéditeur, et pour un envoi à un groupe, qui utilise le numéro du groupe ; obligatoire pour un message de service et pour un modèle créé par votre espace de travail, et doit être un numéro appartenant à votre espace de travail
template.slugchaînenon**Un slug de modèle que votre espace de travail peut envoyer ; les slugs gérés par Bird commencent par bird_
template.languagechaînenon*Balise de langue du modèle (en, pt-BR) ; omettez-la pour envoyer la langue par défaut du modèle
template.parametersobjetnonRemplit les variables du modèle par nom, par exemple ref ou code ; 16 Ko au maximum, et bird est réservé
template.componentstableaunonL'alternative à parameters, avec une entrée par composant : type vaut header, body, button ou carousel, et un carousel prend cards au lieu de parameters
template.components[].parameters[].namechaînenon†L'espace réservé que cette valeur remplit, par exemple ref ; obligatoire pour un modèle à paramètres nommés, où il doit correspondre aux noms déclarés par le modèle, et omis pour un modèle positionnel
textobjetnon**Message de service {body, preview_url?} en texte brut (jusqu’à 4 096 caractères) ; voir Texte brut
image, video, audio, sticker, documentobjetnon**Message de service multimédia avec url (et caption ou filename en option, lorsque le type les prend en charge) ; voir Messages de service
locationobjetnon**Repère {latitude, longitude, name?, address?} ; voir Localisations
contact_cardstableaunon**Jusqu'à 5 objets de contact vCard structurés ; voir Fiches de contact
interactiveobjetnon**Texte du corps et un type de contenu sur lequel appuyer ; il s'agit d'un message de service, qui nécessite donc une fenêtre de service ouverte. Voir Messages interactifs
in_reply_to_message_idchaînenonID d'un message WhatsApp détenu par cet espace de travail, cité dans le message que vous envoyez ; renvoyé lors des lectures. Voir Citer un message
tagstableaunonJusqu'à 20 libellés {name, value} ; nom ≤ 32 caractères, valeur ≤ 64, noms uniques
referencechaînenon1 à 255 caractères ; correspondance exacte pour ?reference= dans la liste des messages
metadataobjetnonJSON arbitraire, jusqu'à 2 Ko une fois sérialisé

* language est facultatif ; si vous l'omettez, le modèle est envoyé dans sa langue par défaut. † name est obligatoire sur chaque paramètre d'un modèle à paramètres nommés. Omettez-le pour un modèle positionnel. Consultez Composants et paramètres. ** Incluez exactement un élément parmi template ou un champ de contenu de message de service (text, image, video, audio, sticker, document, location, contact_cards, interactive) ; consultez Messages de service.

Le modèle asynchrone : ce que signifie 202

Un envoi réussi renvoie 202 Accepted avec un ID de message et status: accepted. La réponse 202 n'est renvoyée qu'une fois l'envoi accepté de manière durable : un envoi n'est jamais accepté puis abandonné en silence. Les échecs définitifs que vous pouvez corriger échouent immédiatement avec une 422 : un destinataire invalide, un slug ou une langue de modèle inconnus, des paramètres qui ne correspondent pas, ou un message de service envoyé alors que la fenêtre de service client est fermée (WhatsAppServiceWindowClosed). Un portefeuille sans fonds n'en fait pas partie : l'envoi est accepté, puis le message se termine en rejected avec insufficient_balance lorsque Bird tente de le facturer. La remise effective a lieu de manière asynchrone : le message passe à sent quand nous le transmettons à WhatsApp, puis à un statut final (delivered ou failed) à l'arrivée de l'accusé, signalé par les événements, les webhooks et les endpoints de lecture. Un accusé de lecture apparaît séparément, sous forme d'un horodatage read_at et d'un événement whatsapp.read, et non comme un statut.

Une remarque sur la confidentialité : pour les modèles de catégorie authentication, la réponse API ne renvoie jamais les valeurs renseignées. L'écho 202 et toutes les lectures ultérieures contiennent un tableau components vide pour ces messages, afin qu'un code de vérification ne réapparaisse jamais.

Réessayer en toute sécurité

Envoyez l'en-tête Idempotency-Key avec une valeur unique par envoi logique pour pouvoir réessayer sans risque. Si votre première requête a réussi mais que vous n'avez jamais reçu la réponse (délai dépassé, connexion interrompue), la renvoyer avec la même clé renvoie le résultat d'origine au lieu d'envoyer et de facturer un message en double. La réponse rejouée contient un en-tête Idempotency-Replay. Consultez idempotence pour le format des clés et leur durée de conservation.

Recevoir la réponse

Les messages entrants arrivent sur la même ressource que les messages sortants, et chacun d'eux réinitialise la fenêtre de service. Recevoir des messages WhatsApp explique comment les lire via API, récupérer les médias envoyés par un contact et utiliser le webhook whatsapp.received.

Coût et facturation

WhatsApp est facturé par message, selon la catégorie du modèle et le pays du destinataire ; consultez Tarifs WhatsApp. Un message est facturé en deux étapes, à deux moments différents, et l'objet cost du message indique les deux :

ChampDescriptionMoment d'enregistrement
transaction_amountFrais de Bird pour le traitement de l'envoiLorsque Bird traite l'envoi accepté, avant l'expédition
passthrough_amountPart de Meta dans le prix du message, que Bird refacture telle quelleÀ l'arrivée d'un accusé delivered ou read applicable
amountSomme des composantes déjà tariféesAugmente à mesure que chaque composante est enregistrée
currency_codeDevise du portefeuille de votre organisation, commune aux deux composantesAvec la première composante

Les deux montants sont des chaînes décimales, hors taxes.

Remarque : un message de service ne comporte aucune part Meta jusqu’au 30 septembre 2026, et il en va de même pour un modèle utilitaire distribué dans une fenêtre de service client ouverte. À partir du 1er octobre 2026, Meta facture les deux, sauf dans une fenêtre de point d’entrée gratuit de 72 heures, avec 1 000 messages de service gratuits par numéro de téléphone professionnel et par mois : modifications tarifaires d’octobre 2026.

Les deux composantes sont tarifées à partir de données différentes. Les frais de Bird reposent sur la catégorie du modèle envoyé et sur le pays du destinataire. Ce pays provient de l’indicatif du numéro de téléphone ou, pour un envoi adressé à un identifiant utilisateur propre à l’entreprise, du préfixe à deux lettres de cet identifiant. La part de Meta repose sur la catégorie que Meta indique lui-même dans l’accusé de réception applicable, qui peut différer de celle du modèle : Meta peut indiquer authentication-international lorsque ses règles de destination, de localisation de l’entreprise et d’éligibilité s’appliquent. Consultez les tarifs authentication-international de WhatsApp.

La valeur lue par cost dépend de l’avancement du message :

  • Au moment de la réponse 202, cost est null. Rien n’a encore été tarifé.
  • Après le traitement, transaction_amount est renseigné et amount lui est égal. passthrough_amount reste null.
  • Après un accusé de réception delivered ou read applicable, un frais Meta correctement enregistré renseigne passthrough_amount, et amount reflète les composantes enregistrées.

Une composante null signifie qu’aucun montant n’est enregistré dans cette projection ; cela ne prouve pas que le message était gratuit. Une composante explicitement tarifée à zéro affiche "0.00000".

Les deux frais échouent aussi de manière différente. Les frais de Bird échouent en mode fermé : s’ils ne peuvent pas être prélevés après la réponse 202, parce que le portefeuille ne couvre pas l’envoi ou que la route n’a pas de prix configuré, le message se termine en rejected avec le code d’erreur insufficient_balance ou price_not_found, et rien n’est facturé. Un message rejected n’a jamais atteint WhatsApp, ce qui le distingue de failed. La part de Meta échoue en mode ouvert : si le solde du portefeuille est insuffisant ou si le tarif manque à l’arrivée de l’accusé de réception, le frais est ignoré sans annuler l’état observé du message. Le second frais ne retarde jamais la distribution.

Un message facturé par Bird conserve ces frais sortants même si la distribution échoue ensuite. Les frais Meta sont traités à partir d’un callback delivered ou read applicable, lorsque Meta indique une tarification standard avec une catégorie et une destination identifiables. Les deux chemins de callback utilisent la même identité de frais et s’appuient sur la déduplication du service de facturation. Rapprochez les accusés de réception rejoués des enregistrements de facturation, au lieu de considérer la projection du message comme un justificatif de débit définitif. Une tarification de service ou de point d’entrée gratuit peut ramener la composante Meta à zéro ; une composante non résolue ne prouve pas que le message était gratuit.

Utilisez le registre de facturation pour le rapprochement financier. Les champs cost du message sont des projections des frais et peuvent être en retard ou incomplets. Consultez les métriques WhatsApp pour la différence entre les observations de messages et les enregistrements de facturation.

Les événements WhatsApp ne contiennent pas le coût. Pour lire l’une ou l’autre composante, relisez le message avec GET /v1/whatsapp/messages/{id}.

Étapes suivantes