Aller au contenu principal
Guides Techniques• 7 min de lecture

Webhooks entrants WhatsApp : recevoir et traiter les messages en temps réel

Guide complet sur les webhooks WhatsApp Business API. Comment configurer, recevoir et traiter les messages entrants en temps réel avec exemples de code Node.js et Python.

Un système WhatsApp unidirectionnel qui envoie des messages sans jamais écouter les réponses est un outil limité. La vraie puissance de WhatsApp Business API réside dans la communication bidirectionnelle : vos clients répondent, posent des questions, confirment des commandes, et votre système réagit en temps réel. C'est précisément le rôle des webhooks. Pour les entreprises africaines qui traitent des dizaines ou des centaines d'interactions quotidiennes via WhatsApp, une architecture webhook bien configurée est la colonne vertébrale de tout leur système de communication client.

Comprendre le mécanisme des webhooks Meta

Un webhook est une URL exposée par votre serveur que Meta appelle automatiquement chaque fois qu'un événement se produit sur votre compte WhatsApp Business. Ces événements incluent les messages entrants, les accusés de réception, les changements de statut de message (envoyé, livré, lu) et les mises à jour de profil.

Le flux de données est le suivant : un client envoie un message WhatsApp à votre numéro → Meta reçoit le message → Meta envoie une requête HTTP POST à votre URL webhook → votre serveur traite l'événement et répond avec un code 200 → Meta considère l'événement comme traité.

Si votre serveur répond avec un code autre que 200, ou ne répond pas du tout dans les 20 secondes, Meta retentera l'envoi plusieurs fois selon une logique de backoff. Après plusieurs échecs, les événements peuvent être perdus. C'est pourquoi la robustesse de votre endpoint webhook est critique.

Consultez le guide complet WhatsApp Business API pour les prérequis de configuration sur le tableau de bord Meta Developer.

Configuration initiale : vérification du webhook

Avant de pouvoir recevoir des événements, Meta doit vérifier que votre URL est valide et sous votre contrôle. Ce processus de vérification se fait via une requête GET avec un challenge token :

// Node.js / Express
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) {
        // Renvoyer le challenge pour confirmer la propriété
        return res.status(200).send(challenge);
    }
    
    res.sendStatus(403);
});
# Python / Flask
@app.route('/webhook', methods=['GET'])
def verify():
    if (request.args.get('hub.mode') == 'subscribe' and
        request.args.get('hub.verify_token') == os.getenv('VERIFY_TOKEN')):
        return request.args.get('hub.challenge'), 200
    return 'Forbidden', 403

Choisissez un VERIFY_TOKEN difficile à deviner (chaîne aléatoire de 32+ caractères). Ce token n'est utilisé que pour cette vérification initiale, pas pour l'authentification des webhooks en production.

Structure du payload entrant

Chaque événement webhook de Meta suit une structure JSON cohérente. Comprendre cette structure est essentiel pour un traitement fiable :

{
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
      "changes": [
        {
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "221771234567",
              "phone_number_id": "PHONE_NUMBER_ID"
            },
            "contacts": [
              {
                "profile": { "name": "Amadou Diallo" },
                "wa_id": "221771234567"
              }
            ],
            "messages": [
              {
                "from": "221771234567",
                "id": "wamid.HBgNMjIxNzcxMjM0NTY3FQIAERgSM...",
                "timestamp": "1693900000",
                "text": { "body": "Bonjour, je voudrais commander" },
                "type": "text"
              }
            ]
          },
          "field": "messages"
        }
      ]
    }
  ]
}

Les champs clés à extraire sont from (numéro de l'expéditeur), id (identifiant unique du message), timestamp (horodatage Unix), type (text, image, audio, video, document, location, interactive, button) et le contenu spécifique selon le type.

Traitement robuste des différents types de messages

Un handler de webhook production-ready doit gérer tous les types de messages. Voici une implémentation complète en Node.js :

app.post('/webhook', express.json(), (req, res) => {
    // Vérification de la signature (OBLIGATOIRE en production)
    const signature = req.headers['x-hub-signature-256'];
    const body = JSON.stringify(req.body);
    const expected = 'sha256=' + crypto
        .createHmac('sha256', process.env.APP_SECRET)
        .update(body)
        .digest('hex');
    
    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
        return res.sendStatus(401);
    }
    
    // Répondre immédiatement avec 200 AVANT le traitement
    res.sendStatus(200);
    
    // Traiter de manière asynchrone
    processWebhookPayload(req.body).catch(err => 
        console.error('Webhook processing error:', err)
    );
});

async function processWebhookPayload(body) {
    if (body.object !== 'whatsapp_business_account') return;
    
    for (const entry of body.entry || []) {
        for (const change of entry.changes || []) {
            const { messages = [], statuses = [] } = change.value || {};
            
            // Traiter les messages entrants
            for (const message of messages) {
                await handleMessage(message, change.value.contacts?.[0]);
            }
            
            // Traiter les statuts de livraison
            for (const status of statuses) {
                await handleStatus(status);
            }
        }
    }
}

async function handleMessage(message, contact) {
    const { from, type, id, timestamp } = message;
    const senderName = contact?.profile?.name || 'Client';
    
    // Déduplication : vérifier si ce message_id a déjà été traité
    if (await isAlreadyProcessed(id)) {
        console.log(`Message ${id} already processed, skipping`);
        return;
    }
    
    // Marquer comme traité immédiatement
    await markAsProcessed(id);
    
    switch (type) {
        case 'text':
            await handleTextMessage(from, message.text.body, senderName);
            break;
        case 'image':
            await handleMediaMessage(from, 'image', message.image, senderName);
            break;
        case 'document':
            await handleMediaMessage(from, 'document', message.document, senderName);
            break;
        case 'interactive':
            await handleInteractiveMessage(from, message.interactive);
            break;
        case 'location':
            await handleLocationMessage(from, message.location, senderName);
            break;
        case 'button':
            // Réponse à un bouton de template
            await handleButtonReply(from, message.button);
            break;
        default:
            console.log(`Unhandled message type: ${type}`);
    }
}

async function handleStatus(status) {
    const { id, status: deliveryStatus, timestamp, recipient_id } = status;
    
    // Mettre à jour le statut en base de données
    await updateMessageStatus(id, deliveryStatus, timestamp);
    
    if (deliveryStatus === 'failed') {
        const error = status.errors?.[0];
        console.error(`Message ${id} failed: ${error?.title} (${error?.code})`);
        // Logique de retry ou d'alerte ici
    }
}

La clé dans cet exemple : répondre avec 200 AVANT de traiter le payload. Meta attend une réponse rapide. Si votre traitement est lent, Meta peut considérer le webhook comme en échec. Déplacez la logique lourde dans une queue asynchrone.

Déduplication et idempotence

Meta peut envoyer le même événement plusieurs fois en cas de problème réseau ou de retry. Votre système doit être idempotent : traiter deux fois le même message ne doit pas créer deux réponses ou deux entrées en base de données.

// Redis-based deduplication
const redis = require('redis');
const client = redis.createClient();

async function isAlreadyProcessed(messageId) {
    const exists = await client.exists(`processed:${messageId}`);
    return exists === 1;
}

async function markAsProcessed(messageId) {
    // TTL de 24h pour ne pas encombrer Redis indéfiniment
    await client.setEx(`processed:${messageId}`, 86400, '1');
}

Pour les flux de chatbot avancés, combinez cette architecture webhook avec l'automatisation WhatsApp et les guides sur les chatbots WhatsApp.

FAQ

Q: Combien de temps Meta attend-il une réponse avant de retenter un webhook ?

R: Meta attend 20 secondes maximum pour une réponse HTTP. Si votre serveur ne répond pas dans ce délai, Meta réessaie selon un schéma exponentiel sur une période allant jusqu'à 7 jours. C'est pourquoi il faut impérativement retourner un 200 immédiatement et traiter le payload de manière asynchrone.

Q: Mon URL webhook doit-elle obligatoirement être en HTTPS ?

R: Oui, Meta exige HTTPS avec un certificat SSL valide (signé par une autorité de certification reconnue, pas auto-signé). Les certificats Let's Encrypt gratuits sont acceptés. Les URLs HTTP simples ou avec des certificats invalides seront rejetées lors de la vérification.

Q: Comment tester les webhooks en développement local sans déployer sur un serveur ?

R: Utilisez ngrok (ngrok http 3000) ou Cloudflare Tunnel pour exposer votre serveur local via une URL HTTPS publique. Alternativement, l'outil "Webhooks" dans le tableau de bord Meta Developer permet d'envoyer des événements de test directement vers votre URL de webhook enregistrée.

Q: Que se passe-t-il si mon serveur webhook est hors ligne pendant quelques heures ?

R: Meta réessaie les événements non confirmés pendant 7 jours. À votre retour en ligne, vous recevrez les événements manqués dans l'ordre. Assurez-vous que votre logique de déduplication est robuste pour éviter les doublons. Si vous êtes hors ligne plus de 7 jours, les événements sont définitivement perdus.

Q: Comment souscrire uniquement à certains types d'événements WhatsApp ?

R: Dans le tableau de bord Meta Developer, sous la section Webhooks de votre application, vous pouvez sélectionner précisément quels champs vous souhaitez recevoir : messages (messages entrants), message_deliveries, message_reads, message_reactions, etc. Abonnez-vous uniquement aux événements dont vous avez besoin pour réduire la charge.


Votre infrastructure webhook est robuste ? Passez à l'étape suivante et centralisez toute votre communication WhatsApp avec Whakup : une plateforme qui s'appuie sur les mêmes webhooks pour vous offrir analytics, gestion d'équipe et automatisation sans friction.

#webhooks whatsapp#messages entrants whatsapp#whatsapp api temps réel#webhook configuration#traitement messages
Pablo Lenormand
Pablo LenormandCo-fondateur & CPO

Co-fondateur et Chief Product Officer de Whakup, Pablo conçoit les fonctionnalités qui permettent aux marques africaines de maximiser leur impact sur WhatsApp.

🚀

Prêt à passer à l'action ?

Essayez Whakup gratuitement pendant 15 jours. Aucune carte bancaire requise.

Démarrer l'essai gratuit