Aller au contenu
OmniChannels.ai

Webhooks WhatsApp : événements, sécurité et nouvelles tentatives

Comment fonctionnent les webhooks WhatsApp : événements de messages et de statuts, gestionnaire fiable, signature Meta, nouvelles tentatives et idempotence.

Mis à jour le 10 min de lecture

Par OmniChannels.ai Editorial Team · Relu par BroadNet Messaging Solutions Team

Intégration

Réponse courte

Un webhook WhatsApp est un point de terminaison HTTPS de votre serveur qui reçoit des événements : statuts de livraison des messages que vous envoyez et messages que vos clients vous adressent. Avec OmniChannels.ai, les statuts arrivent sous la forme SUBMITTED, SENT, DELIVERED, READ, FAILED ou REJECTED. Stockez le corps brut, répondez rapidement en 2xx, dédoublonnez par identifiant de message et traitez de façon asynchrone. Meta signe ses propres webhooks ; notre documentation ne définit aucun en-tête de signature : sécurisez donc votre point de terminaison au niveau du transport et de l'application.

Sur cette page
  1. Que sont les webhooks WhatsApp et pourquoi les utiliser ?
  2. Quels événements et statuts vais-je recevoir ?
  3. Comment configurer le point de terminaison ?
  4. À quoi ressemble la charge utile ?
  5. Comment écrire un gestionnaire fiable ?
  6. Comment sécuriser un webhook WhatsApp ?
  7. Que se passe-t-il quand la livraison échoue ?
  8. Comment récupérer les événements manquants ?
  9. Exemple concret : le pipeline de notifications d'un distributeur
  10. Quelles sont les erreurs courantes ?
  11. Comment OmniChannels.ai vous aide

Points clés

  • Les webhooks poussent les statuts et les messages entrants vers votre point de terminaison HTTPS pour que vous n'ayez pas à interroger ; les statuts WhatsApp sont normalisés en SUBMITTED, SENT, DELIVERED, READ, FAILED et REJECTED.
  • Un gestionnaire fiable fait trois choses dans l'ordre : stocker l'événement brut, renvoyer un 2xx, puis traiter de façon asynchrone et idempotente.
  • Les statuts peuvent arriver dans le désordre ou en double : classez-les et ne laissez jamais un SENT tardif écraser un DELIVERED déjà stocké.
  • Meta signe les webhooks envoyés aux applications avec un en-tête X-Hub-Signature-256, mais la spécification de l'API OmniChannels.ai ne définit aucune signature de webhook : utilisez donc HTTPS, un chemin impossible à deviner et la validation du corps.
  • Si des événements ont été manqués, lisez le statut WhatsApp par lot via le point de terminaison bulk-insights plutôt que de renvoyer les messages.

Ce guide s'adresse aux ingénieurs qui doivent recevoir de façon fiable les événements WhatsApp : statuts de livraison des messages qu'ils envoient et messages entrants des clients. Il explique le fonctionnement des webhooks, ce qu'il faut construire dans le gestionnaire, en quoi la sécurité diffère entre les webhooks de Meta et les nôtres, et que faire quand des événements disparaissent.

Que sont les webhooks WhatsApp et pourquoi les utiliser ?

Les webhooks WhatsApp sont des rappels HTTP. Lorsqu'un message change d'état ou qu'un client vous écrit, la plateforme envoie un POST avec un corps JSON vers votre point de terminaison. Vous n'avez pas à interroger la plateforme, et vous voyez les événements au fur et à mesure.

Deux familles d'événements comptent :

  • Les événements de statut des messages que vous avez envoyés : en file d'attente, envoyé, délivré, lu, en échec.
  • Les messages entrants des clients, qui ouvrent ou prolongent la fenêtre de service client de 24 heures. Voir notre guide de la fenêtre.

Un appel d'envoi vous indique seulement que les messages ont été mis en file d'attente. C'est le webhook qui vous apprend ce qui est arrivé à chaque destinataire.

Quels événements et statuts vais-je recevoir ?

Sur OmniChannels.ai, chaque canal rapporte les mêmes statuts normalisés : un seul gestionnaire peut donc servir WhatsApp, SMS et les autres.

Statut Signification Final ?
SUBMITTED Accepté par la plateforme et mis en file d'attente Non
SENT Remis au canal Non
DELIVERED Confirmé sur l'appareil du destinataire Généralement ; READ peut suivre
READ Ouvert par le destinataire, là où le canal rapporte les lectures Oui
FAILED Non délivré ; l'événement indique un motif lorsqu'il est disponible Oui
REJECTED Refusé, par exemple contenu bloqué ou expéditeur non enregistré Oui

Traitez les statuts comme une progression qui peut arriver dans le désordre, et attendez-vous à de nouvelles valeurs avec le temps. Ne plantez pas sur un statut que vous ne reconnaissez pas. Voir statuts de livraison des messages expliqués.

Comment configurer le point de terminaison ?

Exposez un point de terminaison HTTPS, communiquez l'URL à votre fournisseur, puis testez avec votre propre numéro.

  1. Créez un point de terminaison qui accepte POST avec un corps JSON, par exemple https://api.example.com/webhooks/omnichannels.
  2. Envoyez l'URL à notre équipe ou à l'administrateur de votre compte pour la configurer sur votre compte. Les revendeurs peuvent avoir un webhook distinct par sous-compte.
  3. Envoyez un message de test à votre propre numéro et confirmez que les événements arrivent.

Pendant le développement, un tunnel comme ngrok donne à votre serveur local une URL HTTPS publique. Si vous connectez aussi une application directement à Meta, la vérification du point de terminaison par Meta est une étape distincte : une requête GET portant un jeton de vérification et une valeur hub.challenge, à laquelle vous répondez 200 avec le challenge, comme décrit dans la documentation Meta sur les points de terminaison de webhook.

À quoi ressemble la charge utile ?

Elle varie selon le canal. La spécification de l'API ne définit pas de schéma de webhook unique : journalisez d'abord le corps brut et associez les champs dont vous avez besoin à partir d'événements réels de votre compte. L'exemple montre le type d'informations que porte un événement de livraison et n'est qu'illustratif.

{
  "messageId": "2603121407537573731",
  "channel": "SMS",
  "destination": "447700900123",
  "status": "DELIVERED",
  "timestamp": "2026-10-10T09:15:42Z",
  "reason": null
}

Ne codez pas sur ces noms de champs sans vérifier un événement réel. Demandez à votre ingénieur d'onboarding des exemples de charges utiles par canal et conservez-les comme jeux de test. Pour la documentation plus large, voir la référence des webhooks.

Comment écrire un gestionnaire fiable ?

Faites trois choses dans l'ordre : stocker l'événement brut, accuser réception par un 2xx, puis traiter de façon asynchrone. Le gestionnaire ci-dessous suit le schéma de notre documentation.

import express from "express";

const app = express();

// Capturez le corps brut pour pouvoir le stocker exactement tel que reçu.
app.post(
  "/webhooks/omnichannels",
  express.json({ verify: (req, _res, buf) => { req.rawBody = buf.toString("utf8"); } }),
  async (req, res) => {
    try {
      await db.webhookEvents.insert({ receivedAt: new Date(), body: req.rawBody });
    } catch (err) {
      return res.sendStatus(500); // échec du stockage : laisser l'événement être réessayé
    }
    res.sendStatus(200);          // accuser réception rapidement
    queue.publish("status-events", req.body); // faire le vrai travail ailleurs
  },
);

// Worker : traitement idempotent
async function processEvent(event) {
  const id = event.messageId ?? event.message_id ?? event.id; // à adapter selon le canal
  const status = event.status;
  const rank = { SUBMITTED: 1, SENT: 2, DELIVERED: 3, READ: 4, FAILED: 4, REJECTED: 4 };

  const current = await db.messages.findOne({ providerId: id });
  if (!current) return; // message inconnu : le journaliser et enquêter
  if ((rank[status] ?? 0) <= (rank[current.status] ?? 0)) return; // doublon ou événement périmé

  await db.messages.update({ providerId: id }, { status, updatedAt: new Date() });
}
from datetime import datetime, timezone
from flask import Flask, request

app = Flask(__name__)

RANK = {"SUBMITTED": 1, "SENT": 2, "DELIVERED": 3, "READ": 4, "FAILED": 4, "REJECTED": 4}


@app.post("/webhooks/omnichannels")
def omnichannels_webhook():
    raw = request.get_data(as_text=True)
    try:
        db.webhook_events.insert({"received_at": datetime.now(timezone.utc), "body": raw})
    except Exception:
        return "", 500           # échec du stockage : laisser l'événement être réessayé
    queue.publish("status-events", raw)  # traiter de façon asynchrone
    return "", 200               # accuser réception rapidement

Les objets db et queue sont votre propre stockage et votre propre file de tâches. La table rank est ce qui rend le traitement idempotent : un événement en double ou périmé n'est pas une avancée, il est donc ignoré.

Règles pour un gestionnaire fiable

  • Répondez en quelques secondes. Ne faites aucun travail lourd avant de répondre.
  • Ne renvoyez 2xx qu'après le stockage de l'événement. Si le stockage échoue, renvoyez 5xx.
  • Dédoublonnez par identifiant de message. Le même statut peut arriver plusieurs fois.
  • Ne renvoyez jamais un message depuis le gestionnaire. Décidez des nouveaux essais dans une tâche distincte, après vérification du statut.
  • Conservez le corps brut pour le dépannage et pour rejouer les événements après une correction.

Comment sécuriser un webhook WhatsApp ?

Cela dépend de qui écrit à votre point de terminaison. Meta signe les webhooks qu'il envoie aux applications que vous enregistrez chez lui. La spécification de l'API OmniChannels.ai ne définit aucun en-tête de signature de webhook : n'en attendez donc pas.

Meta vers votre propre application OmniChannels.ai vers votre point de terminaison
Signature En-tête X-Hub-Signature-256, selon la documentation de Meta Aucune définie dans la spécification de l'API
Vérification HMAC-SHA256 de la charge utile brute avec le secret de votre application, comparé à l'en-tête Sans objet
Vos contrôles Vérifier la signature à chaque POST HTTPS avec certificat valide, chemin impossible à deviner, validation stricte du corps

Pour le schéma de Meta, la documentation de Meta indique de générer un hash HMAC-SHA256 de la charge utile JSON avec le secret de votre application et de le comparer à la valeur qui suit sha256= dans l'en-tête X-Hub-Signature-256. Le HMAC lui-même est défini par la RFC 2104. Calculez-le sur les octets bruts, pas sur un objet re-sérialisé, et comparez en temps constant.

Pour notre point de terminaison :

  • Utilisez uniquement HTTPS, avec un certificat valide.
  • Utilisez un chemin long et impossible à deviner, et changez-le s'il fuit.
  • Validez la forme du corps et rejetez tout ce qui est inattendu.
  • Traitez les événements comme des notifications. Avant une action irréversible, comme un remboursement ou un verrouillage antifraude, confirmez l'état par un appel d'interrogation.

Que se passe-t-il quand la livraison échoue ?

Les nouvelles tentatives dépendent de l'expéditeur. La documentation de Meta indique qu'il réessaie une livraison échouée à fréquence décroissante pendant jusqu'à 7 jours, ce qui peut produire des notifications en double. C'est une raison d'être idempotent même si vous voyez rarement des doublons. Pour les événements de notre plateforme, confirmez le comportement de réessai avec votre ingénieur d'onboarding au lieu de supposer qu'il est identique à celui de Meta.

Dans les deux cas, construisez en prévoyant la perte autant que la duplication : votre point de terminaison peut être indisponible pendant un déploiement, et une règle de pare-feu peut faire disparaître des requêtes en silence.

Comment récupérer les événements manquants ?

Interrogez le lot, ne renvoyez pas le message.

Besoin Appel Remarques
Lire le statut WhatsApp par destinataire GET /whatsapp/message/bulk-insights Filtrer un lot par status ou destination ; paginé avec page et size
Renvoyer les accusés de livraison SMS POST /sms/resendDlr Filtrer par startDate, endDate, senderId et messageStatus
curl "https://api.broadnet.me/whatsapp/message/bulk-insights?batch_Id=2609091405000000001&page=0&size=100" \
  -H "Authorization: Bearer $BROADNET_TOKEN"

batch_Id est l'identifiant renvoyé par l'appel d'envoi, et page commence à zéro. Lancez un rapprochement nocturne qui lit les lots de la veille et comble les trous.

Checklist de dépannage

  1. L'URL est-elle configurée sur le bon compte ou sous-compte ?
  2. Le point de terminaison est-il joignable en HTTPS avec un certificat valide ?
  3. Répond-il 2xx rapidement, ou dépasse-t-il le délai ?
  4. Un pare-feu ou une règle WAF bloque-t-il les requêtes ?
  5. Journalisez-vous le corps brut avant l'analyse, pour qu'une erreur d'analyse ne puisse pas masquer l'événement ?

Exemple concret : le pipeline de notifications d'un distributeur

« Souq Rafiq » (société fictive), enseigne de distribution aux Émirats arabes unis, envoie des suivis de commande par WhatsApp et veut un statut exact dans son outil de commandes.

  1. Les commandes sont envoyées avec sendbulk, et le batchId est stocké sur la commande. Voir envoyer un message WhatsApp par API.
  2. Le point de terminaison de webhook écrit chaque événement dans une table, puis renvoie 200.
  3. Un worker classe les statuts et met à jour l'état du message de la commande.
  4. Les événements FAILED créent une tâche pour le support avec le motif, et un repli SMS est envisagé selon le repli entre canaux.
  5. Les messages entrants des clients sont acheminés vers la boîte de réception partagée pour que les agents puissent répondre dans la fenêtre.
  6. Une tâche nocturne appelle bulk-insights pour les lots de la veille et corrige tout ce qui a été manqué.

Quelles sont les erreurs courantes ?

  • Du travail lourd avant de répondre, qui provoque des délais dépassés et des livraisons répétées.
  • Accuser réception avant de stocker, ce qui perd des événements quand le stockage échoue.
  • Se fier à l'ordre. Un SENT tardif qui écrase DELIVERED.
  • Coder sur la charge utile illustrative au lieu d'événements réels.
  • Re-sérialiser le JSON avant de vérifier une signature, ce qui modifie les octets.
  • Renvoyer depuis le gestionnaire, ce qui peut créer une boucle.
  • Limites : ce guide décrit le comportement que nous documentons. La politique de réessai et les champs de charge utile de votre compte doivent être confirmés lors de l'onboarding.

Comment OmniChannels.ai vous aide

OmniChannels.ai pousse les statuts de livraison et les messages entrants de WhatsApp, SMS, RCS et d'autres canaux vers votre webhook dans un format normalisé unique, avec des webhooks distincts par sous-compte pour les revendeurs. BroadNet est un Meta Business Solution Provider officiel : les exigences de webhook propres à Meta sont donc gérées pour vous, et vous travaillez avec notre API. Lisez la référence des webhooks et la documentation de l'API WhatsApp, ou voyez comment les événements apparaissent dans la boîte de réception partagée.

Questions fréquentes

Qu'est-ce qu'un webhook WhatsApp ?

Un webhook WhatsApp est une URL de votre serveur que la plateforme appelle en HTTP POST chaque fois que quelque chose se produit, comme un message délivré, lu ou en échec, ou un client qui vous écrit. Il remplace l'interrogation périodique. Vous exposez un point de terminaison HTTPS, le communiquez à votre fournisseur et traitez les événements JSON qu'il reçoit.

Comment vérifier la signature d'un webhook WhatsApp ?

Pour un webhook que Meta envoie directement à votre propre application, la documentation de Meta indique de calculer un HMAC-SHA256 de la charge utile brute avec le secret de votre application et de le comparer à l'en-tête X-Hub-Signature-256. Pour le webhook OmniChannels.ai, la spécification de l'API ne définit aucun en-tête de signature : appuyez-vous donc sur HTTPS, un chemin impossible à deviner et la validation du corps.

Que se passe-t-il si mon point de terminaison de webhook est indisponible ?

La documentation de Meta indique qu'il réessaie les livraisons échouées à fréquence décroissante pendant jusqu'à 7 jours, ce qui peut produire des doublons. Pour les événements de notre plateforme, confirmez le comportement de réessai avec votre ingénieur d'onboarding, et utilisez le point de terminaison bulk-insights pour lire le statut WhatsApp par destinataire d'un lot. Pour le SMS, le point de terminaison resendDlr renvoie les accusés.

Comment gérer les événements de webhook en double ?

Rendez le traitement idempotent. Recherchez le message par son identifiant, comparez le rang du statut entrant à celui du statut stocké et ignorez les événements qui ne constituent pas une avancée. Appliquer deux fois le même événement doit avoir le même effet qu'une seule fois. Stockez le corps brut de chaque événement pour pouvoir le rejouer après la correction d'un bogue.

Quelle doit être la rapidité de réponse de mon point de terminaison ?

En quelques secondes. Stockez l'événement brut, renvoyez un 2xx et faites le vrai travail dans une file ou un worker. N'appelez pas d'autres services, n'envoyez pas de messages et n'exécutez pas de requêtes lourdes avant de répondre. Si le stockage échoue, renvoyez un 5xx pour que l'expéditeur puisse réessayer, au lieu d'accuser réception d'un événement perdu.

Peut-on utiliser un webhook pour envoyer une réponse automatiquement ?

Oui, mais pas depuis le gestionnaire lui-même. Accusez réception de l'événement, puis décidez de la réponse dans une tâche distincte. Dans la fenêtre de service client de 24 heures, vous pouvez envoyer un message libre ; en dehors, il faut un modèle approuvé. Ne renvoyez jamais un message depuis le gestionnaire de webhook lui-même, pour éviter les boucles et les doublons.

Sources

  1. Présentation des webhooks — WhatsApp Business Platform, Meta for Developers
  2. Créer un point de terminaison de webhook — WhatsApp Business Platform, Meta for Developers
  3. RFC 2104 : HMAC: Keyed-Hashing for Message Authentication