Webhooks de WhatsApp Cloud API sur localhost : la poignée de vérification du Verify-Token, les tentatives et la configuration du secret de l'application
Configurez les webhooks WhatsApp Cloud API sur localhost. Gérez la poignée hub.challenge verify-token de Meta, les vérifications X-Hub-Signature-256, les tentatives et le secret de l'application.

Quick answer
Webhooks de WhatsApp Cloud API sur localhost : configuration du jeton de vérification: webhook testing answer
For local webhook testing, run your app locally, expose it with a public HTTPS tunnel, and paste the stable callback URL into the provider dashboard.
How do I test webhooks on localhost?
Start your local server, open a public HTTPS tunnel to that port, configure the provider webhook URL, and inspect events in your local logs.
Why does a stable webhook URL matter?
Stable URLs prevent provider dashboards from needing manual callback updates every time you restart a tunnel.
Vous démarrez votre bot WhatsApp sur localhost:3000, ouvrez un tunnel, collez l’URL publique dans le Meta App Dashboard, cliquez sur Vérifier et enregistrer, et obtenez une erreur rouge. Ou ça fonctionne une fois, vous redémarrez le tunnel le lendemain matin, et le tableau de bord vous demande de vérifier à nouveau.
Les deux problèmes partagent une cause: l’intégration des webhooks de Meta est une poignée de main (handshake), pas un simple interrupteur “envoyez-moi des événements”. Meta envoie d’abord une requête GET contenant une valeur hub.challenge. Ce n’est qu’après y avoir répondu correctement qu’elle commence à livrer des requêtes POST signées. Si vous ne les confirmez pas avec un 200, Meta réessaie pendant jusqu’à 7 jours sur la WhatsApp Business Platform.
Ce guide couvre la poignée de main, le comportement des retries et la configuration du secret de l’application, puis montre pourquoi un sous-domaine stable transforme une corvée récurrente en une inscription unique.
TL;DR
- Meta vérifie votre URL de rappel avec un
GETcontenanthub.mode,hub.verify_tokenethub.challenge. Retournez HTTP 200 avec la valeur du challenge, mais uniquement si le token correspond. - Le token de vérification est une chaîne que vous inventez. Le secret de l’application est émis par Meta. Ce sont des identifiants différents.
- La vérification seule ne fournit rien. Vous devez aussi vous abonner au champ
messages. - WhatsApp réessaie les livraisons échouées pendant jusqu’à 7 jours, attendez-vous à des doublons et à des événements périmés.
- Meta envoie un nouveau
GETde vérification chaque fois que vous définissez ou modifiez l’URL de rappel ou le token de vérification. Une URL de tunnel qui change signifie donc refaire la poignée de main. Un sous-domaine stable vous permet de vous inscrire une fois.
Pourquoi Meta ne peut pas appeler localhost
Les serveurs de Meta sont sur Internet public; votre ordinateur portable est derrière un NAT sans ports entrants ouverts. Meta exige aussi un certificat TLS valide sur votre endpoint, et sa documentation indique que les certificats auto-signés ne sont pas supportés.
Une intégration locale nécessite donc trois choses:
- Un serveur qui répond à la fois à
GET(vérification) et àPOST(évènements) sur un seul chemin. - Une URL HTTPS publique transférant vers ce serveur.
- Comme le tableau de bord stocke une URL de rappel par app, une URL qui reste identique entre les sessions.
Étape 1 : La poignée-token de vérification
Dans le Meta App Dashboard, ouvrez WhatsApp → Configuration. Si vous avez créé votre app avec le cas d’usage “Connect with customers through WhatsApp”, le panneau se trouve sous Use cases → Customize → Configuration à la place. Vous entrez une Callback URL et un Verify token. Le token est une chaîne que vous choisissez; Meta ne le fournit pas. Meta envoie une requête de vérification chaque fois que vous définissez ou modifiez l’un des champs :
GET /webhook?hub.mode=subscribehub.verify_token=YOUR_TOKENhub.challenge=1158201444
| Paramètre | Signification |
|---|---|
hub.mode |
Toujours subscribe pendant la vérification |
hub.verify_token |
La chaîne que vous avez saisie dans le tableau de bord |
hub.challenge |
Une valeur aléatoire générée par Meta |
Meta décrit la condition de réussite: répondre avec le statut 200 et la valeur du hub.challenge. Si vous répondez autre chose, Meta considère le point de terminaison comme non vérifié et n’envoie pas de webhooks.
app.get('/webhook', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
if (mode === 'subscribe' && token === process.env.VERIFY_TOKEN) {
// Réponse brute du challenge, texte brut, sans enveloppe JSON
return res.status(200).type('text/plain').send(challenge);
}
return res.sendStatus(403);
});
En FastAPI vous ne pouvez pas nommer un paramètre Python hub.mode, alors utilisez Query(alias="hub.mode"). PHP a un travers lié: il convertit les points dans les noms de paramètres de requête en underscores, donc lisez hub_mode, hub_verify_token et hub_challenge là-bas.
Échecs fréquents de la poignée de main
| Symptôme | Cause probable | Correctif |
|---|---|---|
| “Callback URL or verify token couldn’t be validated” | réponse JSON, statut non-200, ou décalage d’un caractère dans le token | Renvoyez le challenge brut en texte clair avec 200 |
| Le navigateur affiche une erreur de paramètre manquant | Vous avez ouvert l’URL sans la chaîne de requête de Meta | Attendu ; seul le GET de Meta porte le hub.challenge |
| Délai d’expiration lors de la vérification | Serveur ou tunnel pas encore lancé | Démarrez le serveur, puis le tunnel, puis cliquez sur vérifier |
| La poignée de main passe, mais rien n’arrive | Aucun champ webhook souscrit | Souscrivez au messages |
La ligne de délai est spécifique à localhost. Meta appelle votre URL en direct lorsque vous enregistrez, donc votre serveur et votre tunnel doivent être opérationnels.
Jeton de vérification vs secret de l’application
Les gens les confondent constamment, et Meta les utilise pour des tâches différentes.
- Le token de vérification est choisi par vous et utilisé dans la poignée
GET. Le guide WhatsApp de Hookdeck indique qu’il n’est utilisé qu’une seule fois, lors de l’installation. - Le secret de l’application est délivré par Meta, trouvé sous Paramètres de l’application → Général, et est la clé utilisée pour signer chaque livraison d’événement.
Conservez-les dans des variables d’environnement séparées, par exemple VERIFY_TOKEN et META_APP_SECRET. Si vous utilisez le token de vérification là où le secret de l’application appartient, chaque vrai message échouera à la validation.
Chaque POST transporte un en-tête X-Hub-Signature-256 sous la forme sha256=<hex>, une HMAC-SHA256 du payload signée avec le secret de l’application. Meta dit que la validation est optionnelle mais recommandée. Calculez le HMAC sur les octets bruts de la requête, avant que tout middleware JSON n’accède au corps. Hookdeck note que Meta signe une forme échappée en Unicode du payload, de sorte qu’un corps que vous avez parsé et ré- sérialisé ne correspondra pas.
const crypto = require('crypto');
// express.raw() conserve les octets exacts signés par Meta
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.META_APP_SECRET)
.update(req.body)
.digest('hex');
const received = req.get('x-hub-signature-256') || '';
const valid = received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
if (!valid) return res.sendStatus(401);
res.sendStatus(200); // accuser d’abord
queue.push(JSON.parse(req.body)); // puis traiter asynchroniquement
});
La signature ne porte pas d’horodatage. Le guide Hookdeck sur les webhooks de Facebook rappelle qu’un payload signé et capturé pourrait être rejoué et vérifié à nouveau, il faut donc dédupliquer sur les IDs de message plutôt que de considérer la signature comme preuve de fraîcheur.
Étape 2 : Abonnez-vous au champ messages
La vérification prouve que l’URL est à vous; elle ne définit pas vos événements. Après sa réussite, le tableau de bord affiche une liste des champs de webhooks. Pour un chatbot ou une inbox, abonnez-vous au messages, qui transporte les messages entrants des clients et l’état des messages que vous envoyez (envoyé, livré, lu, échoué).
La documentation de Meta liste les autorisations : whatsapp_business_messaging pour les webhooks messages et whatsapp_business_management pour tous les autres webhooks.
Si les événements de test du tableau de bord arrivent mais pas les vrais messages, vérifiez que votre app est bien souscrite au WABA lui-même. Hookdeck et Webhook Relay rapportent ce mode d’échec: l’URL est vérifiée, mais l’app n’a jamais été souscrite au WABA. La solution est POST /{WABA_ID}/subscribed_apps, et GET sur le même endpoint le confirme. Confirmez aussi que votre app est en mode Live, car Meta note que certains webhooks ne sont pas envoyés aux apps en mode Dev.
Étape 3 : Réessais, doublons et la fenêtre de 7 jours
Meta considère tout ce qui n’est pas HTTP 200 comme une livraison échouée. La fenêtre de réessai dépend du produit :
- WhatsApp Business Platform : la documentation de Meta indique que la livraison est réessayée immédiatement puis à fréquence décroissante jusqu’à 7 jours, après quoi les webhooks non accusés de réception sont supprimés.
- Webhooks Graph API génériques : la page de démarrage des webhooks décrit des réessais sur 36 heures, après quoi les mises à jour non reconnues sont supprimées.
Ces produits décrivent des comportements différents, il n’y a donc pas de contradiction. Pour WhatsApp, prévoyez environ 7 jours. Une exception: pour les numéros utilisant la configuration “No Storage” de Meta, les réessais des webhooks sont limités à 1 heure, et un webhook entrant indisponible est supprimé avec le code d’erreur 131035.
Meta avertit également que les réessais s’appliquent à tous les apps souscrits aux webhooks du WABA, ce qui peut générer des duplicatas de notifications. Cela a quatre conséquences.
Reconnaissez d’abord, travaillez ensuite. Renvoyez 200, puis effectuez les tâches lentes (écritures en base, appels LLM, réponses) depuis une file d’attente. Meta ne publie pas un délai universel unique, et les docs BSP varient. Par exemple, 360dialog indique une limite dure de cinq secondes pour un 200, alors gardez votre handler rapide.
Éliminez les doublons. Identifiez les messages entrants par l’ID du message (wamid...) et les événements d’état par l’ID d’état plus la valeur de l’état, car un message sortant produit légitimement sent, delivered et read.
Ne faites pas confiance à l’ordre d’arrivée. Hookdeck note que WhatsApp ne garantit pas l’ordre, donc un statut read peut arriver avant delivered. Utilisez le champ timestamp à l’intérieur de chaque événement plutôt que votre heure de réception.
Attendez-vous à des événements périmés après une panne locale. Si votre ordinateur portable est fermé pendant la nuit et le tunnel est en panne, Meta réessaie l’URL enregistrée et un flux d’anciens événements peut arriver lorsque vous vous reconnectez. Prolongez votre capacité de traitement. Meta indique que les payloads de webhook peuvent atteindre 3 Mo, et un 413 de votre serveur compte comme une livraison échouée que Meta réessaie.
Meta n’offre pas d’API pour récupérer des données historiques de webhook, donc journalisez les payloads bruts à la réception si vous en avez besoin plus tard.
Pourquoi un sous-domaine stable change le flux de travail
Meta stocke une URL de rappel par app, et la modification revient à refaire la poignée de main. Si votre tunnel donne un nom d’hôte aléatoire à chaque démarrage, chaque matin devient: démarrer le serveur, démarrer le tunnel, copier l’URL, la coller dans le dashboard, cliquer sur vérifier pendant que votre serveur est en marche, et espérer que le token ne présente pas d’erreur de frappe. Hookdeck souligne le même point sur les tunnels dans son guide WhatsApp: l’URL change à chaque redémarrage à moins de payer pour un sous-domaine fixe.
Un sous-domaine stable réduit tout cela à une seule inscription. Vérifiez https://yourname.example-tunnel.dev/webhook une fois, et vous pouvez redémarrer le tunnel, redémarrer, ou changer d’ordinateur sans toucher à la configuration de Meta, tant que le même nom pointe vers votre machine. Les services de tunnel avec sous-domaines réservés, InstaTunnel parmi eux, existent pour cette raison. Vérifiez que le nom persiste après les redémarrages, pas seulement lors des reconnections dans une même session.
Il aide aussi pour le scénario de réessais ci-dessus: les événements réessayés vont à l’adresse enregistrée, donc un nom d’hôte stable permet à une reconnexion de reprendre là où Meta s’est arrêtée.
Gardez dev et prod séparés grâce aux overrides
Si une même app Meta dessert les deux environnements, ne changez pas l’URL au niveau de l’app lorsque vous basculez. Meta prend en charge des URLs de rappel alternées par WABA ou par numéro de téléphone d’entreprise. Lorsqu’un champ pris en charge est déclenché, Meta vérifie d’abord le numéro de téléphone, puis le WABA, puis revient à l’URL de rappel de l’app.
curl -X POST "https://graph.facebook.com/$GRAPH_API_VERSION/$TEST_WABA_ID/subscribed_apps" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"override_callback_uri": "https://dev.example-tunnel.dev/webhook",
"verify_token": "'"$DEV_VERIFY_TOKEN"'"
}'
Pour un seul numéro, envoyez un objet webhook_configuration avec les deux mêmes champs à POST /{PHONE_NUMBER_ID}. D’après les docs d’override de Meta:
- L’URL est limitée à 200 caractères, et l’URL et un token de vérification sont obligatoires.
- Les overrides s’appliquent uniquement à certains champs, y compris
messages. Les webhooks modèles (commemessage_template_status_update) et les webhooks au niveau du compte (commeaccount_update) vont toujours vers l’URL de l’application. - Vous pouvez inspecter la configuration active avec
GET /{PHONE_NUMBER_ID}?fields=webhook_configuration, qui montre le numéro de téléphone, le WABA et les URL de l’application. - Souscrire à nouveau l’app au WABA sans paramètres du corps supprime l’override au niveau du WABA.
Utilisez la version Graph API que votre app cible à la place de $GRAPH_API_VERSION. Un nom d’hôte permanent dev. constitue une cible d’override idéale pour votre numéro de test, tandis que la production conserve sa propre URL.
Note sur le changement mTLS en 2026
Si vous avez activé le mTLS sur votre abonnement webhook, Meta a annoncé que, à partir du 31 mars 2026, son certificat client serait signé par une nouvelle CA détenue par Meta. Le nom commun reste client.webhooks.fbclientcerts.com. L’annonce de Graph API v25 expliquait que le certificat précédent signé par DigiCert ne pouvait pas être renouvelé car DigiCert déprécie l’authentification EKU du client. La racine de ce certificat a expiré le 15 avril 2026. Les développeurs ont été invités à ajouter meta-outbound-api-ca-2025-12.pem à leur magasin de confiance avant la date limite. Un serveur qui applique le mTLS et ignore cette mise à jour aurait cessé de recevoir des webhooks, et la solution est un changement du trust-store.
Meta indique également que le mTLS est activé par application, et non par compte de messagerie ou par numéro de téléphone. La plupart des configurations localhost n’utilisent pas le mTLS. Si vous souhaitez restreindre le trafic entrant, Meta suggère le mTLS plutôt que de mettre en liste blanche ses plages IP, car ces adresses changent périodiquement.
Dépannage : webhook ne reçoit pas de messages
- Tester la poignée à la main. Demandez votre URL avec
?hub.mode=subscribehub.verify_token=YOUR_TOKENhub.challenge=123et confirmez que123revient en texte brut avec le statut 200. - Comparer les tokens caractère par caractère, y compris les espaces en fin.
- Confirmer l’abonnement
messages, puis vérifierGET /{WABA_ID}/subscribed_apps. - Vérifier le mode de l’app. Certains webhooks ne sont pas envoyés lorsque l’app est en mode Dev.
- Rechercher une override qui masque l’URL au niveau de l’app.
- Vérifier le tunnel. Les retries font passer un tunnel down comme un problème Meta.
- Vérifier que la validation de la signature n’exclut pas les POST valides à cause d’un secret incorrect ou d’un corps mal sérialisé.
FAQ
Quel est le token de vérification des webhooks WhatsApp ?
Une chaîne que vous choisissez et saisissez dans le Meta App Dashboard. Meta la renvoie sous forme de hub.verify_token, et votre serveur la compare à la valeur stockée avant d’échoer le hub.challenge.
Que doit renvoyer le point de vérification ?
HTTP 200 avec la valeur du hub.challenge comme corps de la réponse. Le retourner en texte brut, sans enveloppe JSON, est l’approche sûre. Toute autre réponse laisse le point de vérification non vérifié.
Combien de temps Meta réessaie les webhooks WhatsApp échoués ?
La documentation de Meta sur WhatsApp indique jusqu’à 7 jours, avec une fréquence décroissante (1 heure pour les numéros No Storage). Les webhooks Graph API génériques utilisent une fenêtre plus courte de 36 heures.
Dois-je ré-vérifier si je change mon port local ?
Non, tant que l’URL publique et le chemin restent les mêmes, car Meta ne connaît que l’adresse publique. Meta ré-vérifie chaque fois que vous définissez ou modifiez l’URL de rappel ou le token de vérification, donc un nouveau nom d’hôte ou chemin signifie une nouvelle poignée de main.
Conclusion
Le modèle de Meta comporte trois parties: une poignée de main GET sécurisée par un token de vérification que vous avez inventé, des livraisons POST signées avec le secret de l’application émis par Meta, et un réseau de réessais sur 7 jours qui récompense les acknowledgements rapides et les gestionnaires idempotents. Le développement sur localhost est pénible uniquement lorsque l’URL publique change continuellement. Résolvez cela avec un sous-domaine stable, inscrivez-vous une fois, abonnez-vous à messages, et gardez le token de vérification et le secret de l’application séparés.
Sources
- Create a webhook endpoint, WhatsApp Business Platform (Meta)
- Webhooks overview, WhatsApp Business Platform (Meta)
- Webhook overrides (Meta)
- Get started with webhooks, Graph API (Meta)
- WhatsApp Business Platform, No Storage (Meta)
- Introducing Graph API v25 (Meta, Feb 2026)
- WhatsApp Cloud API Webhooks: Setup, Verify Token and Payloads (Webhook Relay)
- Guide to WhatsApp Webhooks (Hookdeck)
- Guide to Facebook Webhooks (Hookdeck)
- Receiving messages via Webhook (360dialog)
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.