Tous les articles
Événements de messagerie mobile passant par une vérification de webhook de type Meta et un tunnel sécurisé vers une application localhost.
WhatsApp Cloud APIMeta webhookslocalhostwebhook security

Tester les webhooks WhatsApp Cloud API sur localhost

Pour tester un webhook WhatsApp Cloud API sur localhost, exposez votre endpoint local en HTTPS, implémentez le challenge GET de Meta, puis vérifiez le X-Hub-Signature-256 de chaque requête POST à partir du corps brut. Enregistrez l’URL du tunnel dans votre application Meta, abonnez le compte WhatsApp Business à messages et envoyez un message de test pour recevoir une vraie charge utile sans déploiement.

Les webhooks WhatsApp utilisent deux modes de vérification distincts

La configuration et la livraison des webhooks ne sont pas authentifiées de la même façon. Pendant la configuration, Meta envoie une requête GET contenant hub.mode, hub.verify_token et hub.challenge. Votre endpoint compare le jeton de vérification et renvoie le challenge en texte brut. Les événements arrivent ensuite par requêtes POST, à authentifier en validant la signature HMAC créée avec le secret de l’application Meta.

Le jeton de vérification est une chaîne aléatoire que vous choisissez ; ce n’est ni le jeton d’accès WhatsApp ni le secret de l’application. Renvoyer le challenge prouve que vous contrôlez l’URL de rappel, mais n’authentifie pas les futures requêtes POST. Le guide officiel des webhooks WhatsApp décrit la configuration du rappel, les abonnements et les champs.

Créer un endpoint avec l’App Router de Next.js

La route suivante gère les deux phases. Lire les données POST avec request.text() conserve exactement les octets nécessaires à la vérification de signature.

// app/api/webhooks/whatsapp/route.ts
import crypto from 'node:crypto';
import { NextRequest } from 'next/server';

export const runtime = 'nodejs';

export async function GET(request: NextRequest) {
  const mode = request.nextUrl.searchParams.get('hub.mode');
  const token = request.nextUrl.searchParams.get('hub.verify_token');
  const challenge = request.nextUrl.searchParams.get('hub.challenge');

  if (mode === 'subscribe' && token === process.env.META_VERIFY_TOKEN) {
    return new Response(challenge ?? '', { status: 200 });
  }
  return new Response('Forbidden', { status: 403 });
}

export async function POST(request: Request) {
  const rawBody = await request.text();
  const supplied = request.headers.get('x-hub-signature-256') ?? '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.META_APP_SECRET!)
    .update(rawBody)
    .digest('hex');

  const a = Buffer.from(supplied);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return new Response('Invalid signature', { status: 401 });
  }

  const payload = JSON.parse(rawBody);
  await enqueueWhatsAppPayload(payload);
  return new Response('EVENT_RECEIVED', { status: 200 });
}

N’appelez pas request.json() avant de reconstruire le JSON pour le HMAC : espaces, échappement ou ordre des clés peuvent changer et produire un condensé différent. Avec Express, capturez un Buffer avant l’analyseur JSON global. Le guide de vérification des signatures de webhook détaille le traitement du corps brut selon les frameworks.

Démarrer un tunnel et configurer l’URL de rappel

  1. Lancez l’application Next.js localement, généralement avec npm run dev sur le port 3000.
  2. Exécutez npx portpreview 3000 dans un autre terminal.
  3. Attribuez une valeur aléatoire à META_VERIFY_TOKEN et le secret des paramètres Meta à META_APP_SECRET.
  4. Dans le tableau de bord développeur Meta, ouvrez la page Configuration du produit WhatsApp.
  5. Définissez l’URL de rappel sur https://YOUR-TUNNEL.portpreview.dev/api/webhooks/whatsapp et saisissez le même jeton de vérification.
  6. Après validation, abonnez le compte WhatsApp Business au champ messages.

Le tunnel doit rester actif pendant le challenge GET et les livraisons POST suivantes. Une URL d’une ancienne session peut encore être résolue sans rediriger vers votre machine ; vérifiez donc l’URL exacte à chaque changement de tunnel.

Tester séparément le challenge GET

Avant d’utiliser le tableau de bord, reproduisez la requête localement :

curl -i \
  "http://localhost:3000/api/webhooks/whatsapp?hub.mode=subscribe&hub.verify_token=${META_VERIFY_TOKEN}&hub.challenge=123456"

La bonne réponse est un statut 200 avec le corps 123456, ni du JSON ni "123456" entre guillemets. Un jeton erroné doit produire 403. Ne journalisez pas les paramètres de requête, car le jeton de vérification y figure.

Comprendre une charge utile messages avant d’écrire la logique métier

WhatsApp imbrique les données sur plusieurs niveaux. Une notification type contient object: "whatsapp_business_account", un tableau entry, un tableau changes et une modification dont le field vaut messages. Dans value, le contenu entrant se trouve dans messages ; les états de livraison, lecture et échec de vos envois se trouvent dans statuses.

for (const entry of payload.entry ?? []) {
  for (const change of entry.changes ?? []) {
    if (change.field !== 'messages') continue;
    for (const message of change.value.messages ?? []) {
      await handleInboundMessage({
        id: message.id,
        from: message.from,
        type: message.type,
        text: message.text?.body,
      });
    }
    for (const status of change.value.statuses ?? []) {
      await updateDeliveryStatus(status.id, status.status);
    }
  }
}

Ne supposez pas que chaque notification contient du texte. Images, audio, documents, positions, réponses interactives, messages système et charges ne contenant qu’un état ont des formes différentes. Utilisez un répartiteur fondé sur message.type, validez les champs facultatifs et conservez les événements inconnus pour examen au lieu de provoquer une erreur.

Vérifier correctement la signature POST

La valeur X-Hub-Signature-256 a la forme sha256=<hex digest>. Calculez le HMAC-SHA256 des octets bruts avec le secret de l’application Meta. Le jeton d’accès WhatsApp, permanent ou temporaire, sert aux appels Graph API et non de clé HMAC. Comparez en temps constant et refusez toute signature absente.

Gardez la vérification active en local. Quiconque connaît l’URL du tunnel peut y envoyer du JSON. Sans contrôle, un faux événement pourrait déclencher des réponses, modifier le CRM ou révéler des données client. Renouvelez le secret s’il a été validé dans Git, affiché ou partagé par erreur.

Accuser réception rapidement et dédupliquer les messages

Renvoyez 200 après authentification et mise en file durable. N’attendez pas le téléchargement de médias, un appel à un LLM ou plusieurs mises à jour de services. Les fournisseurs réessaient si l’accusé échoue et les doublons sont normaux en cas d’incertitude réseau.

Utilisez l’id du message WhatsApp comme clé d’idempotence pour messages entrants et états, avec une contrainte d’unicité sur les ID traités. Un état peut légitimement évoluer d’envoyé à livré puis lu ; dédupliquez chaque transition utile sans supprimer un état ultérieur.

Résoudre les problèmes de configuration du webhook WhatsApp

L’URL de rappel n’a pas pu être validée

Testez la route GET via l’URL publique. Elle doit accepter GET, comparer exactement le jeton et renvoyer uniquement le challenge. Redirections, middleware d’authentification, réécritures de langue ou enveloppe JSON peuvent échouer. Vérifiez aussi que le processus de développement a chargé la variable d’environnement.

La vérification réussit, mais aucun message n’arrive

La vérification du rappel n’abonne pas automatiquement le compte aux champs. Confirmez l’abonnement à messages et que le numéro appartient à la bonne application et au bon compte. En mode développement, utilisez un destinataire autorisé.

Toutes les requêtes POST échouent à la vérification de signature

Les causes habituelles sont l’emploi du jeton d’accès au lieu du secret, le hachage du JSON analysé, l’oubli du préfixe sha256= ou des encodages différents. Journalisez la longueur du corps et la présence de l’en-tête, jamais le secret ni toute la charge client.

Les messages texte fonctionnent, mais pas les médias

Une notification média contient un ID, pas nécessairement les octets du fichier. Récupérez d’abord le média par Graph API avec un jeton valide, puis téléchargez-le. Gardez ce traitement lent hors du chemin d’accusé de réception.

Le endpoint local reçoit des événements en double

Inspectez le statut et la latence de réponse, ajoutez une idempotence durable et rejouez un événement capturé après chaque correction. Le guide de rejeu des webhooks évite l’envoi d’un nouveau message réel à chaque modification.

Protéger les données client pendant les tests locaux

  • Utilisez si possible des numéros de test et des conversations synthétiques.
  • Masquez dans les journaux numéros, corps des messages, URL de médias, contacts et noms de profil.
  • Conservez secret, jetons d’accès et jeton de vérification uniquement dans des fichiers d’environnement ignorés ou un gestionnaire de secrets.
  • Limitez l’accès aux captures du tunnel et supprimez-les après le débogage.
  • Validez les identifiants d’objet, de champ et de compte avant toute action métier.

Un tunnel accélère les itérations, mais apporte aussi des données personnelles proches de la production sur la machine du développeur. Appliquez la liste de contrôle de sécurité des tunnels avant tout test avec de vrais utilisateurs.

Questions fréquentes

Comment tester un webhook WhatsApp Cloud API sur localhost ?
Lancez le gestionnaire localement, exposez-le avec un tunnel HTTPS, enregistrez l’URL publique et le jeton dans Meta, abonnez-vous à messages, puis envoyez un message de test.
Que doit renvoyer un endpoint de vérification WhatsApp ?
Pour une requête GET valide où hub.mode vaut subscribe et où hub.verify_token correspond, renvoyez hub.challenge en texte brut avec le statut HTTP 200.
Comment vérifier les requêtes POST d’un webhook WhatsApp ?
Calculez le HMAC-SHA256 du corps brut exact avec le secret de l’application Meta, préfixez le condensé hexadécimal par sha256= et comparez-le en temps constant à X-Hub-Signature-256.
Pourquoi mon webhook WhatsApp validé ne reçoit-il aucun événement ?
La validation du rappel n’abonne pas tous les champs. Vérifiez l’abonnement du compte WhatsApp Business à messages et la disponibilité du numéro et de l’expéditeur de test pour l’application.