Documentation API Wazzap AI - Webhooks
Table des matières
- Introduction
- Configuration du webhook
- Types d'événements supportés
- Format des livraisons
- Vérification de la signature (HMAC)
- Fiabilité et gestion des échecs
- Exemples de serveurs webhook
- Bonnes pratiques
- Dépannage
Introduction
Les webhooks vous permettent de recevoir en temps réel les messages WhatsApp entrants de votre agent directement sur votre serveur, sans avoir à interroger l'API.
À chaque message reçu par votre agent, Wazzap AI envoie une requête POST signée vers l'URL que vous avez configurée.
Caractéristiques principales
- Livraison en temps réel des messages entrants
- Signature HMAC-SHA256 de chaque livraison (header
X-Wazzap-Signature) - Validation de l'URL par un ping de test à l'enregistrement
- Désactivation automatique après échecs répétés (protection de votre quota)
- Un webhook par clé API, lié à l'agent de la clé
Cas d'usage typiques
- Synchroniser les conversations WhatsApp dans votre CRM
- Déclencher des workflows (Zapier, Make, n8n…) à la réception d'un message
- Construire votre propre interface de support par-dessus Wazzap AI
- Journaliser tous les messages entrants pour analyse
Configuration du webhook
Depuis le dashboard
- Rendez-vous dans Paramètres → Clés API
- Sur la clé API concernée, ouvrez la section Webhook
- Renseignez l'URL HTTPS de votre serveur
- Wazzap AI envoie immédiatement un ping de validation (
webhook.test) — votre serveur doit répondre avec un statut 2xx pour que l'URL soit enregistrée - Copiez le secret HMAC affiché : il sert à vérifier la signature de chaque livraison
Contraintes sur l'URL
| Contrainte | Détail |
|---|---|
| Protocole | https:// uniquement (http refusé) |
| Réseau | Les adresses privées/locales sont refusées (localhost, 127.x, 10.x, 192.168.x, 172.16-31.x, 169.254.x) |
| Validation | L'URL doit répondre 2xx au ping de test, sinon elle n'est pas enregistrée |
| Timeout | 10 secondes par livraison |
Le ping de validation
À l'enregistrement de l'URL, votre serveur reçoit ce payload :
{
"event": "webhook.test",
"apiKeyId": "cm9xxx...",
"agentId": "cm9yyy...",
"data": {
"message": "Ping de validation Wazzap — répondez avec un statut 2xx pour valider ce webhook.",
"timestamp": "2026-07-06T15:00:00.000Z"
}
}
Répondez simplement 200 OK (le corps de votre réponse est ignoré).
Types d'événements supportés
| Événement | Déclencheur | Description |
|---|---|---|
webhook.test |
Enregistrement / test de l'URL depuis le dashboard | Ping de validation. Répondez 2xx. |
message.received |
Message WhatsApp entrant reçu par votre agent | Un contact a envoyé un message à votre numéro WhatsApp connecté. |
⚠️ Ce qui ne déclenche PAS de webhook
- Vos propres envois via l'API
/api/wazzap/send-message - Les messages tapés manuellement depuis le téléphone connecté (messages
fromMe) - Les réponses générées par l'IA de votre agent
- Les accusés de lecture, réactions et statuts
Le webhook est volontairement limité aux messages entrants : c'est l'événement dont vous avez besoin pour réagir à vos contacts. D'autres types d'événements pourront être ajoutés — le champ event du payload vous permet de router chaque livraison et d'ignorer sans erreur les types que vous ne connaissez pas.
Format des livraisons
Headers de la requête
Chaque livraison est un POST avec ces headers :
POST /votre-endpoint HTTP/1.1
Content-Type: application/json
User-Agent: Wazzap-Webhook/1.0
X-Wazzap-Signature: sha256=3f5a8c…
Payload message.received — message texte
{
"event": "message.received",
"agentId": "cm9yyy...",
"organizationId": "cm9zzz...",
"data": {
"conversationId": "cmr9aaa...",
"contactId": "cmr9bbb...",
"phoneNumber": "33612345678",
"contactName": "Jean Dupont",
"message": "Bonjour, je voudrais des informations",
"messageType": "text",
"fromMe": false,
"media": null,
"transcription": null,
"description": null,
"messageId": "3EB0A5B4E7CB234BC78A48",
"timestamp": "2026-07-06T15:18:55.000Z"
}
}
Payload message.received — message vocal (audio)
Les vocaux sont transcrits automatiquement (Whisper). La transcription arrive dans message ET dans transcription ; le fichier audio (hébergé sur notre CDN, lisible publiquement) est dans media.url.
{
"event": "message.received",
"agentId": "cm9yyy...",
"organizationId": "cm9zzz...",
"data": {
"conversationId": "cmr9aaa...",
"contactId": "cmr9bbb...",
"phoneNumber": "33612345678",
"contactName": "Jean Dupont",
"message": "Bonjour, est-ce que le produit est encore disponible ?",
"messageType": "audio",
"fromMe": false,
"media": {
"url": "https://pub-xxxx.r2.dev/wazzap-v2.5/audio/1783351406671-xxx.mp3",
"type": "audio",
"mimeType": "audio/ogg; codecs=opus"
},
"transcription": "Bonjour, est-ce que le produit est encore disponible ?",
"description": null,
"messageId": "false_963835624366@lid_3A4355A4022B54D3A211",
"timestamp": "2026-07-06T15:23:14.000Z"
}
}
💳 Coût : chaque transcription réussie consomme 1 crédit de votre organisation. Si votre solde est à zéro, la transcription est sautée (le vocal est quand même livré avec son media.url, transcription vaut null). Vous pouvez désactiver la transcription à tout moment via le switch « Transcrire les vocaux » dans la configuration du webhook (Paramètres → Clés API → Webhook).
⚠️ Si la transcription échoue (audio inaudible, service indisponible…), aucun crédit n'est débité et la livraison a quand même lieu : transcription vaut null, message contient le texte/caption éventuel (souvent "" pour un vocal), et media.url vous permet de récupérer l'audio pour le traiter vous-même.
Payload message.received — image
Les images reçues sont analysées par la vision IA : la description générée arrive dans description. message contient la caption tapée par le contact (souvent vide).
{
"event": "message.received",
"agentId": "cm9yyy...",
"organizationId": "cm9zzz...",
"data": {
"conversationId": "cmr9aaa...",
"contactId": "cmr9bbb...",
"phoneNumber": "33612345678",
"contactName": "Jean Dupont",
"message": "C'est bien ce modèle ?",
"messageType": "image",
"fromMe": false,
"media": {
"url": "https://pub-xxxx.r2.dev/wazzap-v2.5/images/1783351406671-xxx.jpg",
"type": "image",
"mimeType": "image/jpeg"
},
"transcription": null,
"description": "Photo d'une paire de baskets blanches avec des lacets rouges…",
"messageId": "false_963835624366@lid_3A4355A40BB54D3A211",
"timestamp": "2026-07-06T15:25:02.000Z"
}
}
Champs du payload
| Champ | Type | Description |
|---|---|---|
event |
string |
Type d'événement (message.received) |
agentId |
string |
ID de l'agent qui a reçu le message |
organizationId |
string |
ID de votre organisation |
data.conversationId |
string |
ID de la conversation dans Wazzap AI |
data.contactId |
string |
ID du contact dans Wazzap AI |
data.phoneNumber |
string |
Numéro de l'expéditeur (sans le +) |
data.contactName |
string | null |
Nom du contact (si connu) |
data.message |
string |
Texte propre du message : texte ou caption ; transcription pour un vocal quand elle a réussi ; "" pour un média muet |
data.messageType |
string |
text | image | audio | video | document |
data.fromMe |
boolean |
Sens du message : false = envoyé par le contact (entrant). Toujours false pour message.received — champ explicite, prêt pour de futurs types d'événements |
data.media |
object | null |
Média joint : { url, type, mimeType }. null pour un message texte. url pointe vers notre CDN public (ou l'URL source si l'upload CDN a échoué) |
data.transcription |
string | null |
Audio uniquement : transcription automatique du vocal, null si elle a échoué ou n'a pas été tentée |
data.description |
string | null |
Image uniquement : description générée par la vision IA, null si non analysée |
data.messageId |
string | null |
ID WhatsApp du message (utilisez-le comme clé d'idempotence) |
data.timestamp |
string |
Date d'envoi du message (ISO-8601) |
Votre réponse
Répondez avec un statut 2xx (idéalement 200) le plus vite possible — traitez le message de façon asynchrone si votre logique est lente. Toute réponse non-2xx ou un timeout (>10 s) est compté comme un échec de livraison.
Vérification de la signature (HMAC)
Chaque livraison est signée avec le secret HMAC de votre webhook :
X-Wazzap-Signature: sha256=<hex(HMAC-SHA256(secret, corps_brut_de_la_requête))>
⚠️ Vérifiez toujours la signature avant de traiter une livraison — c'est votre garantie que la requête vient bien de Wazzap AI et non d'un tiers. Calculez le HMAC sur le corps brut (les octets reçus, avant tout parsing JSON).
Fiabilité et gestion des échecs
| Règle | Valeur | Détail |
|---|---|---|
| Timeout de livraison | 10 s | Au-delà, la livraison est comptée en échec |
| Échecs consécutifs tolérés | 20 | Le compteur (webhookFailCount) s'incrémente à chaque échec |
| Après 20 échecs consécutifs | Webhook désactivé | Réactivez-le depuis le dashboard après avoir réparé votre endpoint |
| Après un succès | Compteur remis à zéro | Un seul succès efface la série d'échecs |
⚠️ Pas de re-livraison automatique : une livraison échouée n'est pas rejouée. Si votre endpoint était indisponible, récupérez les messages manqués depuis votre dashboard Wazzap AI.
Exemples de serveurs webhook
Tester rapidement sans coder
Pour valider votre configuration sans écrire de serveur, utilisez un service d'inspection de webhooks (webhook.site, webhooklens.cloud, requestbin…) : collez l'URL générée dans le dashboard, envoyez un message WhatsApp à votre agent depuis un autre téléphone, et observez la livraison arriver.
Transférer vers Slack
app.post('/wazzap-webhook', async (req, res) => {
if (!verifySignature(req)) return res.status(401).end();
res.status(200).json({ ok: true });
const { event, data } = req.body;
if (event !== 'message.received') return;
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `💬 *${data.contactName || data.phoneNumber}* : ${data.message}`
})
});
});
Répondre automatiquement via l'API
app.post('/wazzap-webhook', async (req, res) => {
if (!verifySignature(req)) return res.status(401).end();
res.status(200).json({ ok: true });
const { event, data } = req.body;
if (event !== 'message.received') return;
// Exemple : accusé de réception hors horaires d'ouverture
const hour = new Date().getHours();
if (hour < 9 || hour >= 18) {
await fetch('https://api21.wazzap.ai/api/wazzap/send-message', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.WAZZAP_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
phoneNumber: '+' + data.phoneNumber,
message: 'Merci pour votre message ! Nous vous répondrons dès 9h.',
disableAiResponse: true
})
});
}
});
Bonnes pratiques
1. Répondez vite, traitez après
Votre endpoint a 10 secondes pour répondre. Accusez réception immédiatement (200) puis traitez le message en asynchrone (queue, worker…). Un traitement synchrone lent = timeouts = webhook désactivé au bout de 20 échecs.
2. Vérifiez toujours la signature
Sans vérification HMAC, n'importe qui connaissant votre URL peut vous injecter de faux messages.
3. Soyez idempotent
Utilisez data.messageId comme clé d'idempotence : si vous recevez deux fois le même messageId, ne traitez qu'une seule fois.
4. Ignorez les événements inconnus
Routez sur le champ event et répondez 200 aux types que vous ne gérez pas — de nouveaux types pourront apparaître.
5. Surveillez votre endpoint
Un endpoint qui tombe silencieusement = webhook désactivé après 20 échecs. Monitorez sa disponibilité, et vérifiez régulièrement le statut du webhook dans le dashboard.
Dépannage
Je ne reçois aucun webhook :
- Vérifiez que le webhook est bien activé dans le dashboard (il a pu être désactivé après 20 échecs)
- Rappelez-vous que seuls les messages entrants déclenchent le webhook — pas vos envois API ni vos messages tapés depuis le téléphone
- Vérifiez que votre agent WhatsApp est bien connecté
- Testez avec un service d'inspection (webhook.site) pour isoler le problème côté Wazzap ou côté serveur
L'URL est refusée à l'enregistrement :
- L'URL doit être en
https://et publiquement accessible (pas de localhost/IP privée) - Votre serveur doit répondre 2xx au ping
webhook.testen moins de 10 s
La vérification de signature échoue :
- Calculez le HMAC sur le corps brut de la requête, pas sur le JSON re-sérialisé
- Vérifiez que vous utilisez le bon secret (ré-affichable dans le dashboard)
- Le header est au format
sha256=<hex>— comparez la chaîne complète
Contact
Pour toute question :
- Consultez d'abord cette documentation
- Contactez nous à l'adresse contact@Wazzap.ai
