Documentation de référence webhook WhatsApp — tableau des champs d'abonnement, spécification d'en-tête HMAC et chronologie des réessais sur fond de terminal développeur sombre
Sections : Fondamentaux du webhook · Référence complète des propriétés · Tous les champs d'abonnement · Événements par champ · Programme de réessai · Comportement des réponses HTTP · Spécification HMAC-SHA256 · WABA vs niveau téléphone · Livraison at-least-once · Schémas de payload · Liste de vérification de sécurité · Catalogue d'erreurs · Format normalisé SocialHook · FAQ

Fondamentaux du webhook

L'API Cloud WhatsApp livre des événements à votre application via un système de webhook basé sur le push. Plutôt que d'interroger un endpoint pour de nouveaux événements, les serveurs de Meta envoient via POST un payload JSON à une URL que vous enregistrez — votre endpoint webhook. Votre serveur traite le payload et renvoie HTTP 200 pour accuser réception.

Deux méthodes HTTP opèrent sur la même URL :

  • GET — défi de vérification unique lors de l'enregistrement. Votre endpoint doit renvoyer le paramètre de requête hub.challenge en texte brut.
  • POST — notifications d'événements en direct. Chaque message entrant, mise à jour de statut et événement de compte arrive ici.

Les contraintes les plus importantes à internaliser avant d'écrire du code :

  • Répondre dans les 20 secondes — tout ce qui est plus lent déclenche un réessai. Accusez réception immédiatement, traitez de façon asynchrone.
  • Livraison at-least-once — le même événement peut arriver plusieurs fois. Votre handler doit être idempotent.
  • Aucune garantie d'ordre — les événements peuvent arriver hors séquence. Ne supposez jamais un ordre chronologique.
  • Limite de taille de payload de 3 Mo — les payloads webhook individuels ne dépasseront pas 3 Mo. Les fichiers médias ne sont pas inclus en ligne.
  • HTTPS requis — Meta rejette les endpoints HTTP simples. Certificat SSL valide requis.

Référence complète des propriétés du webhook

Propriété Valeur / Spécification Notes
ProtocoleHTTPS uniquementCertificat SSL valide requis. HTTP rejeté catégoriquement.
DirectionFournisseur → Consommateur (push)Meta pousse vers vous. Aucun polling requis.
Méthode de vérificationGET + hub.verify_tokenUnique lors de l'enregistrement. Renvoyer hub.challenge en texte brut.
Authentification sur les événementsX-Hub-Signature-256 HMAC-SHA256Signé avec App Secret. Vérifier chaque POST.
Méthode HTTP pour les événementsPOSTToujours POST. Jamais GET pour les événements en direct.
Format des donnéesJSONContent-Type: application/json
Délai de réponse20 secondesDépassement déclenche un réessai. Renvoyer 200 immédiatement.
Critère de succèsHTTP 200Tout non-200 déclenche le mécanisme de réessai.
Limite de taille du payload3 MoMédias non en ligne — référencés par ID.
Durée de réessaiJusqu'à 7 joursBackoff exponentiel. Voir le programme complet ci-dessous.
Garantie de livraisonAt-least-onceDoublons possibles. Rendre le handler idempotent.
Garantie d'ordreAucuneLes événements peuvent arriver hors ordre chronologique.
Niveaux de webhookNuméro de téléphone + WABALe numéro de téléphone a priorité sur le fallback WABA.
Relecture manuelleNon prise en chargeAucune relecture native. Événements perdus après 7 jours.
Algorithme de signatureHMAC-SHA256Clé = App Secret. Entrée = octets bruts du body.
En-tête de signatureX-Hub-Signature-256Format : sha256=<hex>
Livraisons concurrentesPlusieurs par secondeLes numéros à haut volume reçoivent de nombreux événements/seconde.

Tous les champs d'abonnement du webhook

Vous vous abonnez à des champs individuels dans le Meta Developer Dashboard (WhatsApp → Configuration → Webhooks → Manage). L'abonnement à messages est obligatoire pour la plupart des intégrations. Chaque champ représente une catégorie d'événements — abonnez-vous uniquement à ce dont vous avez besoin pour réduire le volume de payload.

Champ Couverture Priorité
messages Tous les messages clients entrants + toutes les mises à jour de statut sortantes (sent / delivered / read / failed). Le champ principal pour toute intégration de messagerie. S'abonner toujours
account_update Violations de politique (ACCOUNT_VIOLATION) et restrictions actives (ACCOUNT_RESTRICTION) sur vos numéros de téléphone. Essentiel pour la surveillance en production. Recommandé
message_template_status_update Approbation, rejet, mise en pause et désactivation des templates. Se déclenche lorsque Meta change le statut d'un template que vous avez soumis. Recommandé
phone_number_quality_update Changements de notation de qualité (GREEN / YELLOW / RED) pour vos numéros de téléphone. La qualité affecte votre niveau de messagerie. Abonnez-vous pour détecter la dégradation tôt. Recommandé
phone_number_name_update Événements d'approbation ou de rejet du nom d'affichage. Se déclenche lorsque Meta examine un nom que vous avez soumis pour votre numéro WhatsApp Business. Situationnel
business_capability_update Changements de niveau de limite de messagerie — lorsque Meta améliore ou rétrograde votre niveau de conversations quotidiennes (1K / 10K / 100K / Unlimited). Situationnel
flows Interactions WhatsApp Flows — événements de soumission de données lorsqu'un utilisateur complète ou interagit avec un Flow attaché à votre numéro. Si vous utilisez Flows
security Événements de PIN de vérification en deux étapes. Se déclenche lorsque le PIN d'un numéro de téléphone est modifié ou désactivé. Optionnel
message_template_components_update Se déclenche lorsque Meta modifie les composants d'un template approuvé (rare — Meta peut ajuster les templates pour se conformer à la politique). Optionnel
account_alerts Alertes de facturation, avertissements de capacité et autres avis opérationnels au niveau du compte de Meta. Optionnel

Événements livrés par champ d'abonnement

Champ messages — types de messages entrants

Le champ messages livre deux catégories : les messages entrants des clients (identifiés par un tableau messages dans l'objet value) et les mises à jour de statut de livraison sortantes (identifiées par un tableau statuses). Vérifiez quel tableau est présent avant de traiter.

msg.typeEmplacement du contenuNotes
textmsg.text.bodyCorps de message en texte brut. Peut inclure des URLs.
imagemsg.image.id, .mime_type, .captionID → résoudre l'URL → télécharger. Légende optionnelle.
audiomsg.audio.id, .voicevoice: true si enregistré dans l'application. Toujours télécharger immédiatement.
videomsg.video.id, .captionLégende optionnelle.
documentmsg.document.id, .filename, .mime_typeNom de fichier inclus — stockez-le.
stickermsg.sticker.id, .animatedLe drapeau animated distingue WebP vs sticker animé.
locationmsg.location.latitude, .longitude, .name, .addressNom et adresse sont optionnels.
contactsmsg.contacts[n].name, .phonesTableau — le client peut partager plusieurs contacts.
reactionmsg.reaction.emoji, .message_idRéférence l'ID du message auquel le client a réagi.
interactivemsg.interactive.type, puis .button_reply ou .list_replyVérifier type avant de lire le sous-objet.
ordermsg.order.catalog_id, .product_itemsWhatsApp Commerce — commande de produit depuis le catalogue.
systemmsg.system.body, .typeÉvénements système : le client a changé de numéro, etc.
buttonmsg.button.text, .payloadClic sur un bouton de réponse rapide depuis un message template.
referralmsg.referral.source_url, .source_type, .source_idDonnées de référence d'annonce Click-to-WhatsApp accompagnant le message.

Champ messages — mises à jour de statut sortantes

valeur de statutSignificationChamps supplémentaires
sentMessage accepté par Meta et transféré à WhatsApp. Pas encore livré à l'appareil.timestamp, recipient_id, conversation, pricing
deliveredLe message a atteint l'appareil du destinataire (double coche grise).timestamp, recipient_id, conversation, pricing
readLe destinataire a ouvert la conversation (double coche bleue). Ne se déclenche que si les accusés de lecture sont activés.timestamp, recipient_id
failedÉchec permanent de la livraison. Vérifiez status.errors[0].code pour l'erreur spécifique.tableau errors avec code et title

Programme exact de réessai avec temporisations de backoff

Meta réessaie les livraisons webhook échouées pendant jusqu'à 7 jours en utilisant un backoff exponentiel. « Échoué » signifie que votre endpoint a renvoyé un non-200, a atteint le délai d'attente (a pris plus de 20 secondes) ou était inaccessible. Après 7 jours, l'événement est définitivement rejeté — aucun chemin de récupération n'existe nativement.

1Immédiat (première livraison)
2~5 secondes~5 secondes après la première tentative
3~30 secondes~35 secondes
4~2 minutes~2,5 minutes
5~10 minutes~12 minutes
6~30 minutes~42 minutes
7~2 heures~2 heures 42 minutes
8~6 heures~8,5 heures
9~12 heures~20 heures
10~24 heures~44 heures
11+~24 heuresToutes les 24 h jusqu'à 7 jours
FinaleAprès 7 joursÉvénement définitivement rejeté — aucune récupération
Implication pratique : Si votre serveur est en panne pour maintenance, les événements s'accumulent chez Meta pendant jusqu'à 7 jours. Lorsque votre serveur revient en ligne et commence à renvoyer 200, Meta livre l'arriéré — potentiellement un afflux de milliers d'événements arrivant simultanément. Concevez votre handler webhook et votre file d'attente pour absorber les afflux à haute concurrence sans perdre d'événements ni provoquer de défaillances en cascade.

Comportement des codes de réponse HTTP

Ce que vous renvoyez à Meta détermine si l'événement est considéré comme livré, réessayé, ou s'il déclenche une alerte. L'arbre de décision est plus simple que la plupart des développeurs ne s'y attendent — tout est binaire : 200 signifie succès, tout le reste signifie réessai.

200
Accusé de réception — livraison terminée
Meta considère l'événement comme livré. Aucun réessai programmé. Renvoyez 200 immédiatement après la vérification HMAC, indépendamment du fait que vous ayez terminé le traitement. Déplacez tout le traitement vers une file d'attente asynchrone.
403
Interdit — déclenche un réessai
Renvoyez 403 lorsque la vérification HMAC échoue. Meta traite cela comme une livraison échouée et programme un réessai. Utile pour rejeter explicitement les requêtes qui ne correspondent pas à votre signature — bien qu'en pratique, renvoyez 200 pour les requêtes de sécurité qui passent la vérification et 403 uniquement pour les tentatives de falsification authentiques.
4xx (autres)
Erreur client — déclenche un réessai
Toute réponse 4xx déclenche le mécanisme de réessai de Meta. Ne renvoyez jamais 4xx pour des erreurs de logique métier à l'intérieur de payloads valides — renvoyez toujours 200 pour accuser réception et gérez l'erreur dans votre application. Un 400 ou 404 de votre handler est indiscernable d'une erreur de configuration serveur du point de vue de Meta.
5xx
Erreur serveur — déclenche un réessai
Les erreurs serveur authentiques (plantage, OOM, exception non gérée) renvoient 5xx. Meta réessaie. Le point de conception critique : ne laissez jamais les erreurs de logique métier remonter sous forme de 5xx. Enveloppez tout le traitement dans try-catch, renvoyez 200 pour accuser réception, et journalisez l'erreur en interne. 5xx ne doit se déclencher que sur des défaillances d'infrastructure authentiques.
Délai d'attente
Aucune réponse dans les 20 s — déclenche un réessai
Si votre handler prend plus de 20 secondes pour répondre, Meta le traite comme un échec. C'est le problème de production le plus courant. Chaque opération synchrone (écriture en base de données, appel LLM, requête HTTP) à l'intérieur de votre handler webhook est un risque de délai d'attente. Renvoyez 200 en <100 ms. Poussez tout le reste vers un worker de file d'attente.

Spécification complète de la signature HMAC-SHA256

Chaque requête POST de Meta inclut une signature cryptographique qui vous permet de vérifier que la requête provient réellement de Meta et n'a pas été altérée durant le transfert. Ignorer cette vérification signifie que n'importe quel attaquant découvrant l'URL de votre webhook peut envoyer des données arbitraires à votre application.

Format de l'en-tête

Spécification de l'en-tête
X-Hub-Signature-256
X-Hub-Signature-256: sha256=a1b2c3d4e5f6... Format : sha256= + hex_digest (hexadécimal en minuscules, 64 caractères) Clé : Votre App Secret (PAS votre jeton d'accès) Message : Corps brut de la requête (raw bytes) — AVANT toute analyse JSON Trouver : Meta Developer Dashboard → App Settings → Basic → App Secret

Vérification en Node.js

Node.js
verify.js
const crypto = require('crypto'); function verifyWebhook(rawBody, signatureHeader, appSecret) { if (!signatureHeader?.startsWith('sha256=')) return false; const received = signatureHeader.slice(7); // supprime 'sha256=' const expected = crypto .createHmac('sha256', appSecret) .update(rawBody) // ← Buffer brut, PAS de JSON analysé .digest('hex'); try { return crypto.timingSafeEqual( // prévient les attaques temporelles Buffer.from(received, 'hex'), Buffer.from(expected, 'hex') ); } catch { return false; // différence de longueur → invalide } } // Utilisation avec Express — nécessite le middleware express.raw() app.post('/webhook', (req, res) => { const valid = verifyWebhook( req.body, // Buffer brut req.headers['x-hub-signature-256'], // en-tête process.env.WHATSAPP_APP_SECRET ); if (!valid) return res.sendStatus(403); res.sendStatus(200); enqueue(JSON.parse(req.body)); });

Vérification en Python

Python
verify.py
import hmac, hashlib def verify_webhook(raw_body: bytes, sig_header: str, app_secret: str) -> bool: if not sig_header.startswith("sha256="): return False received = sig_header[7:] # supprime le préfixe 'sha256=' expected = hmac.new( app_secret.encode(), raw_body, # ← octets bruts, PAS de décodage/analyse hashlib.sha256 ).hexdigest() return hmac.compare_digest(received, expected) # sécurisé contre les attaques temporelles
Le bug HMAC n°1 : calculer la signature après l'analyse JSON. JSON.stringify(JSON.parse(body)) produit des octets différents de l'original — les espaces, l'ordre des clés et la précision des nombres peuvent changer. Calculez toujours le HMAC sur les octets exacts reçus dans la requête HTTP, avant toute transformation.

Webhooks au niveau WABA vs au niveau Numéro de téléphone

L'API WhatsApp Cloud prend en charge deux niveaux de configuration de webhook qui interagissent selon un ordre de priorité spécifique. Comprendre cela permet d'éviter la perte silencieuse d'événements lors de la gestion de plusieurs numéros.

AspectWebhook Numéro de téléphoneWebhook WABA
Emplacement configMeta Developer Dashboard → WhatsApp → ConfigurationMeta Business Settings → WhatsApp Accounts → [WABA] → Webhook
PortéeUn numéro de téléphone spécifiqueTous les numéros du WABA
PrioritéPlus haute — prioritairePlus basse — repli uniquement
Comportement de repliSi défini, le webhook WABA ne reçoit pas les événements pour ce numéroReçoit les événements pour les numéros sans webhook spécifique
Idéal pourIntégrations à numéro unique, logique de routage par numéroOpérations multi-numéros, agence gérant de nombreux clients
Routage d'événementsLes événements vont uniquement à cette URLLes événements de tous les numéros non configurés arrivent ici
Champs d'abonnementConfigurés séparémentConfigurés séparément
Modèle Agence : Configurez un seul webhook au niveau WABA pointant vers un point de terminaison central qui répartit les événements par metadata.phone_number_id. N'ajoutez un webhook au niveau Numéro de téléphone que pour les numéros nécessitant un routage différent. C'est plus propre que de maintenir des webhooks séparés par client, et SocialHook prend en charge plusieurs numéros par compte sous un flux d'événements unique et normalisé.

Livraison "At-least-once" — implications et déduplication

Le système de webhooks de Meta garantit qu'un événement donné sera livré au moins une fois. Il ne garantit pas une livraison exactement une fois. Scénario de doublon : votre serveur traite un événement, renvoie un code 200, mais l'accusé de réception est perdu durant le transport avant que le système de Meta ne l'enregistre. Meta réessaie. Votre gestionnaire traite à nouveau le même événement.

Pour un simple bot d'écho, c'est sans conséquence. Pour un agent IA qui déclenche une action CRM, un paiement ou un message sortant, les doublons causent de réels problèmes. Votre gestionnaire doit être idempotent.

Modèle de déduplication

Node.js + Redis
dedup.js
const redis = getRedisClient(); const DEDUP_TTL = 86400; // 24 heures — par sécurité, plus long que la fenêtre de rétention de 7 jours async function processIfNew(messageId, processFn) { const key = `whatsapp:dedup:${messageId}`; // SET ... NX — ne définit la clé que si elle n'existe pas const isNew = await redis.set(key, '1', 'EX', DEDUP_TTL, 'NX'); if (!isNew) { console.log(`Événement en double ignoré : ${messageId}`); return; // déjà traité — saut idempotent } await processFn(); } // Dans votre worker : await processIfNew(msg.id, () => handleMessage(msg));

La valeur msg.id (la chaîne wamid.HBgL...) est unique par message. Les mises à jour de statut utilisent l'ID du message sortant. Les réactions et autres types d'événements ont leurs propres identifiants uniques. Utilisez toujours l'ID spécifique à l'événement — jamais une valeur dérivée — comme clé de déduplication.

Schémas de payload pour les types d'événements clés

Enveloppe de haut niveau (tous les événements)

JSON
top-level-envelope.json
{ "object": "whatsapp_business_account", // toujours cette chaîne "entry": [{ // tableau — généralement 1 entrée "id": "WABA_ID", "changes": [{ "field": "messages", // nom du champ d'abonnement "value": { /* données spécifiques à l'événement */ } }] }] }

Message texte entrant (objet value)

JSON
inbound-text-value.json
{ "messaging_product": "whatsapp", "metadata": { "display_phone_number": "+1 555 000 1234", "phone_number_id": "PHONE_NUMBER_ID" // utiliser pour le routage dans les configs WABA }, "contacts": [{ "profile": { "name": "Alice" }, "wa_id": "15550002345" }], "messages": [{ "id": "wamid.HBgL...", // ID message unique — utiliser pour déduplication "from": "15550002345", // sans préfixe + "timestamp": "1747231892", // string — parseInt() avant utilisation "type": "text", "text": { "body": "Hello!" } }] }

Mise à jour du statut de message sortant (objet value)

JSON
status-update-value.json
{ "messaging_product": "whatsapp", "metadata": { "display_phone_number": "...", "phone_number_id": "..." }, "statuses": [{ // note : 'statuses' et non 'messages' "id": "wamid.HBgL...", // l'ID de votre message sortant "status": "delivered", // sent | delivered | read | failed "timestamp": "1747231900", "recipient_id": "15550002345", "conversation": { "id": "CONVERSATION_ID", "origin": { "type": "service" } // catégorie de tarification }, "pricing": { "billable": true, "pricing_model": "CBP", "category": "service" } }] }

Checklist de sécurité

Vérifier la signature HMAC-SHA256 sur chaque POST
Rejetez avec un code 403 toute requête où l'en-tête X-Hub-Signature-256 est manquant, malformé ou non correspondant. Sans cela, votre point de terminaison accepte des événements falsifiés par n'importe qui.
Utiliser une comparaison "timing-safe" pour la signature
crypto.timingSafeEqual() sous Node.js, hmac.compare_digest() sous Python. Une simple égalité de chaînes (=== / ==) fuit des informations temporelles que les attaquants exploitent pour falsifier les signatures caractère par caractère.
Analyser le corps brut avant le HMAC, pas après
Calculez le HMAC sur les octets bruts du corps de la requête. L'analyse JSON modifie la représentation des octets. Dans Express : middleware express.raw() sur la route du webhook. Dans FastAPI : await request.body() avant toute désérialisation JSON.
Stocker l'App Secret dans les variables d'environnement
L'App Secret est votre clé de signature HMAC. Quiconque le possède peut falsifier des signatures de webhook valides. Stockez-le dans process.env.WHATSAPP_APP_SECRET (Node.js) ou os.environ["WHATSAPP_APP_SECRET"] (Python). Changez-le immédiatement en cas de fuite.
Implémenter un traitement d'événements idempotent
La livraison "At-least-once" implique l'arrivée de doublons. Stockez les identifiants msg.id traités dans Redis avec un TTL de 24h+. Vérifiez avant de traiter. Ignorez les doublons silencieusement — ne renvoyez jamais d'erreur.
Implémenter une limitation de débit (rate limiting)
En fonctionnement normal, Meta envoie des salves lors des périodes de fort trafic. Implémentez une limitation par IP autorisant le débit légitime de Meta tout en bloquant les volumes inattendus provenant d'IP inconnues. 1000 req/min est un plafond raisonnable.
Utiliser un jeton de vérification imprévisible
Le jeton de vérification n'est contrôlé que lors de l'enregistrement, mais utiliser une chaîne prévisible ("mytoken", "test") facilite le sondage de votre installation. Générez une chaîne hexadécimale aléatoire de 32 octets via crypto.randomBytes(32).toString('hex').
Surveiller la fenêtre de tentative de 7 jours
Si votre point de terminaison est injoignable, les événements s'accumulent chez Meta pendant 7 jours avant d'être supprimés. Configurez une surveillance de disponibilité (Uptime Robot, Better Uptime) avec alertes sur échecs HTTP. Alertez dès le premier échec ; n'attendez pas les rapports de messages perdus.

Common errors and fixes

Error / SymptomRoot causeFix
Verification fails at registration Returning JSON instead of plain text, wrong verify token, or returning hub.challenge wrapped in a JSON object Return hub.challenge as plain text with Content-Type: text/plain. Exact string — no JSON wrapping.
HMAC always fails Computing HMAC after JSON parsing, using access token instead of App Secret, double-encoding the body Use express.raw() in Express. Call await request.body() in FastAPI before parsing. Key = App Secret from App Settings → Basic.
Events not arriving at all messages field not subscribed, or webhook not saved/verified Dashboard → WhatsApp → Configuration → Webhooks → Manage → enable messages field. Confirm webhook shows as verified.
Receiving same event multiple times At-least-once delivery — expected behavior, not a bug Implement deduplication using msg.id as Redis key with 24h TTL. Check before processing.
Events arriving out of order No ordering guarantee — by design Use timestamp field to sort when building conversation threads. Never assume sequential delivery order.
Meta retries keep coming Handler returning non-200, timing out (>20s), or crashing Return 200 immediately. Move all processing to async queue. Wrap handler in try-catch. Monitor server stability.
Media download returns 401 Using wrong token or token expired Media downloads require the same access token used for the API. Ensure it's a permanent System User token, not a temporary user token. Check token hasn't been revoked.
messages array missing from payload It's a status update — the array is statuses, not messages Check for value.messages AND value.statuses separately. Both arrive under the messages field subscription.
Sender number has no + prefix Cloud API delivers from without the + prefix (e.g. 15550001234 not +15550001234) Normalize on receipt: '+' + msg.from to get E.164 format. SocialHook normalizes this automatically.
Timestamp is a string, not an integer Cloud API delivers timestamp as a Unix timestamp string Always parseInt(msg.timestamp, 10) (Node.js) or int(msg["timestamp"]) (Python) before date operations.

SocialHook: the managed webhook layer

Everything in this reference — HMAC verification, payload extraction from the nested envelope, timestamp parsing, sender normalization, retry handling, deduplication infrastructure, WABA vs phone-level routing — is infrastructure work your product doesn't differentiate on.

SocialHook handles the entire Cloud API webhook layer and delivers a normalized event to your endpoint, so your application code only ever sees clean, consistent JSON — never the raw nested Meta payload with its quirks.

ConcernRaw Cloud APISocialHook normalized
Message extractionentry[0].changes[0].value.messages[0]Flat message object at top level
Sender format"15550001234" — no + prefix"+15550001234" — E.164 normalized
Timestamp"1747231892" — string1747231892 — integer
HMAC verificationYou implement itDone — signature_verified: true
Retry on your downtimeMeta retries (7 days)SocialHook retries (3x exponential)
Multi-channel formatDifferent schema per platformSame schema: WhatsApp + FB + Instagram
Delivery logsNot availableFull log per event
Monthly costYour server infra costs$50 flat

SocialHook covers WhatsApp, Facebook Messenger, and Instagram DMs — all three Meta messaging channels — under one account, one webhook URL, one normalized payload format. See the full payload reference or start with the 5-minute quickstart.

Common questions

What webhook subscription fields does the WhatsApp Cloud API support?
10+ fields: messages (inbound messages + outbound status), account_update (violations, restrictions), message_template_status_update (approval/rejection), phone_number_quality_update (quality rating), phone_number_name_update (display name), business_capability_update (tier changes), flows (WhatsApp Flows interactions), security (2FA changes), message_template_components_update (template modifications), and account_alerts. Subscribe per-field in the Meta Developer Dashboard.
How long does WhatsApp retry failed webhook deliveries?
Up to 7 days with exponential backoff: ~5s → ~30s → ~2m → ~10m → ~30m → ~2h → ~6h → ~12h → ~24h, then every 24h until 7 days. After 7 days, the event is permanently discarded with no recovery path natively available.
What is at-least-once delivery and how do I handle it?
Meta guarantees events are delivered at least once but may deliver the same event more than once. Make your handler idempotent: store the msg.id in Redis with a 24h TTL using SET ... NX. Before processing any event, check if the ID exists. If it does, skip. If not, add it and process. This prevents duplicate CRM records, double AI responses, and duplicate payments.
What is the X-Hub-Signature-256 header format?
Format: sha256=<64-character lowercase hex>. The hex value is HMAC-SHA256 of the raw request body bytes, using your App Secret as the key (find it in App Settings → Basic — this is different from your access token). Strip the sha256= prefix before comparing. Always use timing-safe comparison.
Why are inbound messages and status updates on the same webhook field?
Both inbound messages and outbound status updates (sent/delivered/read/failed) are delivered under the messages subscription field, but they differ in the value object: inbound events contain a messages array, status updates contain a statuses array. Always check which array is present before processing — attempting to read value.messages[0] on a status update payload returns undefined.
What is the difference between WABA-level and Phone Number-level webhooks?
Phone Number webhooks are configured per number and take priority. WABA webhooks are configured at the WhatsApp Business Account level and act as a fallback for numbers without a Phone Number webhook. For agencies managing multiple client numbers, configure one WABA-level webhook as a central receiver that dispatches by metadata.phone_number_id, and only add Phone Number-level webhooks where different routing is needed.
Voici la dernière partie de la traduction française. Travail terminé, sans compromis sur la structure technique. ```html

Référence lue.
Maintenant, recevez votre premier webhook.

Vous connaissez la spécification. SocialHook gère la vérification HMAC, la normalisation du payload, la logique de tentative et les logs de livraison — votre application ne voit que du JSON propre. Connectez votre numéro en moins de 5 minutes.

Aucune carte de crédit requise · 50 $/mois après l'essai · Annulez à tout moment