Webhooks
Les webhooks sont des notifications automatisées vers des URLs définies qui informent des événements en temps réel. Ils sont utiles car ils permettent une réponse immédiate à des événements spécifiques sans nécessiter d'interrogation continue. Les webhooks aident à maintenir les systèmes efficaces et réactifs.
Vos webhooks sont appelés immédiatement lorsqu'un événement se produit. Si nous ne pouvons pas atteindre votre URL ou si elle répond avec un statut d'erreur, nous relançons l'envoi selon un plan fixe et vérifions automatiquement sur plusieurs jours si le destinataire est en panne durable. Les détails sont décrits plus bas dans Livraison & retries.
Livraison & retries
Quand nous réessayons un webhook
Un webhook n'est relivré que lorsque la réponse de votre serveur indique une erreur temporaire :
- Name
HTTP 429- Type
- rate-limit
- Description
Votre endpoint signale qu'il reçoit actuellement trop de requêtes.
- Name
HTTP 5xx- Type
- erreur serveur
- Description
Concrètement
500,502,503et504, ainsi que les codes d'erreur d'origine Cloudflare520à524(p. ex.521« Web Server Is Down », que Cloudflare renvoie à la place de502pour les domaines proxifiés).
- Name
Erreurs de connexion- Type
- réseau
- Description
Timeouts, échecs de résolution DNS, erreurs TLS/handshake ou une connexion interrompue pendant la requête.
Toutes les autres réponses sont considérées comme finales et ne déclenchent pas de nouvelle tentative. Cela inclut les codes 4xx tels que 400, 401, 403 et 404, car ils indiquent généralement une erreur de configuration côté destinataire qui ne peut pas être résolue par un simple retry. Les réponses dans la plage 2xx et 3xx sont considérées comme réussies.
Timeout de requête
Chaque tentative de livraison a un timeout de 5 secondes. Si votre serveur ne répond pas dans ce délai, nous traitons la tentative comme un échec et planifions un retry selon le plan ci-dessous.
Plan de retry
Si une tentative échoue, la suivante est planifiée après un délai fixe et croissant. Nous effectuons jusqu'à 14 tentatives au total ; au-delà, le job est abandonné. La fenêtre totale de livraison s'étend donc sur environ 3,5 jours.
| Tentative | Délai depuis la tentative précédente |
|---|---|
| 1 (initiale) | immédiatement |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 60 minutes |
| 6 | 2 heures |
| 7 à 14 | 10 heures chacune |
Chaque tentative est signée séparément et porte des en-têtes X-Timestamp, X-Nonce et X-Signature frais, voir ci-dessous Vérifier la signature.
Désactivation automatique en cas de pannes persistantes
En complément des retries individuels, nous surveillons en continu le taux d'erreur quotidien par webhook. Un webhook est marqué comme « problématique » dès que, sur une journée donnée,
- le taux d'erreur du jour est supérieur à 80 % et
- au moins 10 erreurs sont survenues ce jour-là.
À partir de ce moment, le webhook est observé pendant plusieurs jours. Si la situation ne s'améliore pas, le système escalade par paliers :
| Jour | Seuil (taux d'erreur rolling) | Action |
|---|---|---|
| 1 | supérieur à 80 % | E-mail d'avertissement niveau 1 |
| 3 | supérieur à 85 % | E-mail d'avertissement niveau 2 |
| 5 | supérieur à 90 % | E-mail d'avertissement niveau 3 |
| 7 | supérieur à 95 % | Le webhook est désactivé automatiquement, entrée dans l'audit-log et e-mail de désactivation |
Les compteurs journaliers sont calculés à partir du jour de détection du problème.
Après une désactivation automatique
Un webhook désactivé automatiquement reste inactif jusqu'à ce que vous le réactiviez manuellement dans le Dashboard. Après la réactivation, les statistiques repartent de zéro.
Qu'est-ce qui compte comme « réussi » ?
Pour l'évaluation du taux d'erreur, toute réponse qui n'est pas un déclencheur de retry compte comme « ok ». Concrètement, les réponses 2xx, 3xx et même 4xx (à l'exception de 429) sont traitées comme réussies, car elles représentent, du point de vue du système d'envoi, une réponse finale et sans ambiguïté.
Cela signifie : un endpoint qui répond durablement par 404 ne sera pas désactivé automatiquement et devra être supprimé ou corrigé manuellement par vous.
Sécurité
Vérifier l'IP source
Tous les webhooks proviennent de notre IPv4 195.201.160.143 ou de l'IPv6 2a01:4f8:13a:8e7::2. Si cela devait changer, vous trouverez tous les détails supplémentaires dans notre Changelog.
Vérifier la signature
Tous les webhooks vers votre serveur sont signés avec la clé de signature de votre compte. Vous pouvez utiliser ces données pour valider l'authenticité des requêtes de notre part pour chaque webhook reçu et éviter la duplication ou les requêtes frauduleuses. Pour cela, vérifiez la signature comme décrit sur la page Signature.
Nous vous envoyons les en-têtes suivants dans chaque webhook :
- Name
X-Signature- Type
- string
- Description
La signature générée par nous
- Name
X-Timestamp- Type
- integer
- Description
Timestamp auquel la signature a été créée par nous
- Name
X-Nonce- Type
- string
- Description
Chaîne générée aléatoirement avec 32 caractères
Chaque tentative de livraison est signée séparément. L'envoi initial, chaque retry automatique du plan de retry et un renvoi manuel depuis le débogueur de webhooks portent chacun leur propre X-Timestamp, leur propre X-Nonce et une X-Signature correspondante. Un retry qui arrive plusieurs heures après l'événement porte donc toujours un horodatage actuel et un nonce qui n'a jamais été utilisé auparavant.
De ce fait, la fenêtre de 30 secondes pour l'horodatage et l'unicité du nonce décrites sur la page Signature s'appliquent aussi aux retries. Conservez votre fenêtre de validation à 30 secondes et ne l'élargissez pas pour les retries.
Le champ webhook_timestamp de la charge utile correspond à l'heure de l'événement, et non à l'heure de la tentative de livraison. Lors d'un retry, il diffère volontairement de l'en-tête X-Timestamp. Validez toujours la valeur de l'en-tête, jamais le champ de la charge utile.
Exemples en charge utile JSON
SMS
SMS entrant (sms_mo)
{
"data": {
"id": "681590",
"sender": "SMS",
"system": "491771783130",
"text": "Hello World",
"time": "1605878104",
"message_type": "SMS"
},
"webhook_event": "sms_mo",
"webhook_timestamp": "2020-12-02 11:55:44"
}
Changement de statut SMS (dlr)
{
"data": {
"msg_id": "77149843739",
"status": "TRANSMITTED",
"timestamp": "2021-08-24 08:08:00.000000"
},
"webhook_event": "dlr",
"webhook_timestamp": "2021-08-24T08:08:00+02:00"
}
Suivi de performance (tracking)
{
"webhook_event": "tracking",
"webhook_timestamp": "2022-07-27T07:38:18+02:00",
"data": {
"sms_id": "77182424125",
"sms_label": null,
"tracking_url": "https://svn.me/7oz",
"final_url": "https://www.google.com",
"type": "click",
"total_clicks": 5,
"total_views": 3
}
}
Voice
Changement de statut message vocal (voice_status)
{
"data": {
"callerId": "49176123456789",
"duration": "4",
"id": "284195",
"pricePerMinute": 0.075,
"recipient": "4943160049851",
"status": "completed",
"timestamp": 1629786769
},
"webhook_event": "voice_status",
"webhook_timestamp": "2021-08-24T08:32:50+02:00"
}
Appel vocal entrant (voice_call)
{
"webhook_event": "voice_call",
"webhook_timestamp": "2024-08-02T07:28:59+02:00",
"data": {
"id": 0,
"caller": "4943160049851",
"time": 1722576539,
"system": "4915170517246"
}
}
Signal DTMF (voice_dtmf)
{
"webhook_event": "voice_dtmf",
"webhook_timestamp": "2024-08-02T07:28:59+02:00",
"data": {
"id": 0,
"callerId": "4943160049851",
"recipient": "4943160049851",
"status": "completed",
"system": "4915170517246",
"timestamp": 1722576539,
"duration": 2.76,
"pricePerMinute": 0.045,
"dtmf_digit": 9,
"total_price": 0.045
}
}
RCS
Le message a été livré
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T10:01:07+01:00",
"data":
{
"msg_id": "77233699836",
"status": "DELIVERED",
"timestamp": "1709888466.254410",
"foreign_id": null,
"agent_id": "myfancyagent"
}
}
Le message a été lu
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T10:01:09+01:00",
"data":
{
"msg_id": "77233699836",
"status": "READ",
"timestamp": "1709888468.065783",
"foreign_id": null,
"agent_id": "myfancyagent"
}
}
Rapport de statut d'un message envoyé
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T10:00:18+01:00",
"data":
{
"sender": "4915153952979",
"status": "IS_TYPING",
"agent_id": "myfancyagent"
}
}
Un message texte a été envoyé
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T09:47:12+01:00",
"data": {
"id": 1871353,
"sender": "4915153952979",
"time": "1709870553",
"message_type": "RCS",
"content_type": "text",
"text": "Hello",
"agent_id": "myfancyagent"
}
}
Une réponse suggérée a été sélectionnée
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T05:59:33+01:00",
"data": {
"id": 1870983,
"sender": "4915153952979",
"time": "1709870129",
"message_type": "RCS",
"content_type": "suggestion_response",
"suggestion_response": {
"postbackData": "suggestion_2",
"text": "Suggestion #2",
"type": "REPLY"
},
"agent_id": "myfancyagent"
}
}
Une image a été envoyée
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T08:58:20+01:00",
"data": {
"id": 1871195,
"sender": "4915153952979",
"time": "1709870556",
"message_type": "RCS",
"content_type": "user_file",
"user_file": {
"thumbnail": {
"mimeType": "image/jpeg",
"fileSizeBytes": 10166,
"fileUri": "https://static.seven.io/uploads/rbm/61513d3d/6176746a5177553d/4f36357145416a773452537737696e495a366a68454a42344639574937716e62704156765358664d486e42776730337334675a76522b6d4f574b6e32626632546842493d.jpeg"
},
"payload": {
"mimeType": "image/jpeg",
"fileSizeBytes": 611314,
"fileName": "IMG_20240308_050231_01.jpg",
"fileUri": "https://static.seven.io/uploads/rbm/61513d3d/6176746a5177553d/4f76746e45515768746866717653444e6166336a513852375249614d753637656f414a6f4758624d5358417269306e767331493645372b47446654325a66764b3168453d.jpeg"
}
},
"agent_id": "myfancyagent"
}
}
Un fichier a été envoyé
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T11:07:51+01:00",
"data": {
"id": 1871485,
"sender": "4915153952979",
"time": "1709892233",
"message_type": "RCS",
"content_type": "user_file",
"user_file": {
"payload": {
"mimeType": "application/pdf",
"fileSizeBytes": 18810,
"fileName": "sample (1).pdf",
"fileUri": "https://static.seven.io/uploads/rbm/61513d3d/617678715267303d/4f616c675256696b73524c6c75337a4b5a66336a51705570464e665a752f6d4e70464d37546e504b5458423730556a7337314e70514c3241436650304d616e4b3078493d.pdf"
}
},
"agent_id": "myfancyagent"
}
}
Une localisation a été partagée
{
"webhook_event": "rcs",
"webhook_timestamp": "2024-03-08T11:07:51+01:00",
"data": {
"id": 1871485,
"sender": "4915153952979",
"time": 1709892233,
"message_type": "RCS",
"content_type": "location",
"location": {
"latitude": 54.3216562,
"longitude": 10.1350767
},
"agent_id": "myfancyagent"
}
}
Les messages WhatsApp entrants sont livrés avec l'événement wa_mo. L'objet data contient toujours les mêmes champs de base, quel que soit le contenu envoyé par le client :
- Name
id- Type
- integer
- Description
ID interne du message entrant
- Name
sender- Type
- string
- Description
Numéro de téléphone de l'expéditeur au format international sans le plus initial
- Name
time- Type
- integer
- Description
Horodatage Unix de la réception
- Name
text- Type
- string
- Description
Contenu du message. Texte brut pour les messages texte, une chaîne encodée en JSON pour tous les autres types de contenu
- Name
message_type- Type
- string
- Description
Toujours
WA
- Name
system- Type
- string
- Description
Votre numéro WhatsApp Business auquel le message a été envoyé
Si vous définissez un filtre d'événement sur le webhook, celui-ci est comparé à system. Vous pouvez ainsi répartir les messages par numéro WhatsApp Business.
Message texte entrant (wa_mo)
{
"webhook_event": "wa_mo",
"webhook_timestamp": "2026-08-21T14:50:12+02:00",
"data": {
"id": 4521987,
"sender": "353861234567",
"time": 1755780612,
"text": "Hello World",
"message_type": "WA",
"system": "4915170517246"
}
}
Types de contenu autres que le texte brut
Les médias, positions, contacts, boutons de réponse rapide et réponses interactives sont livrés dans la même enveloppe. Le champ text contient alors une chaîne encodée en JSON dont la clé type indique ce qui a été reçu :
type | Envoyé par le client |
|---|---|
user_file | Image, vidéo, audio, document ou sticker |
location | Une position partagée |
contact | Une carte de contact partagée |
button | Bouton de réponse rapide d'un message modèle |
interactive | Réponse à un message interactif (bouton ou liste) |
Décodez text avec votre parseur JSON avant d'accéder à ces structures. Les messages texte ne sont pas encodés en JSON : vérifiez donc le premier caractère ou utilisez simplement la chaîne brute si le décodage échoue.
Contenu décodé du champ text :
Médias (user_file)
{
"type": "user_file",
"user_file": [
{
"type": "image",
"mediaId": "1234567890123456",
"mimeType": "image/jpeg",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"fileName": "IMG_20260821_145012.jpg",
"fileUri": "https://static.seven.io/uploads/whatsapp/61513d3d/6176746a/4f36357145416a773452537737/IMG_20260821_145012.jpg",
"caption": "Regarde ça"
}
]
}
type est déduit du type MIME et vaut image, video, audio, document ou sticker. Si nous ne pouvons pas télécharger le fichier depuis Meta, fileUri est absent et l'objet contient à la place "downloadFailed": true. Utilisez dans ce cas mediaId pour récupérer le fichier vous-même.
Position
{
"type": "location",
"location": {
"latitude": 54.3216562,
"longitude": 10.1350767,
"name": "seven communications GmbH",
"address": "Alte Weide 7, 24116 Kiel"
}
}
Contact
{
"type": "contact",
"contact": {
"formattedName": "John Doe",
"firstName": "John",
"lastName": "Doe",
"middleName": null,
"phones": [{"phone": "+353861234567", "type": "CELL"}],
"emails": [{"email": "john@example.com", "type": "WORK"}],
"addresses": [],
"company": "Example Ltd",
"birthday": "1985-04-12"
}
}
Bouton de réponse rapide
{
"type": "button",
"button": {
"text": "Confirmer le rendez-vous",
"payload": "confirm_2026_08_21"
}
}
Réponse interactive
{
"type": "interactive",
"interactive": {
"item_id": "option_2",
"title": "Option #2",
"description": "Livraison vendredi"
}
}
Les réactions (réponses par emoji à un message) ne sont pas livrées via wa_mo. Elles sont enregistrées et affichées dans Conversations, mais il n'existe aucun événement webhook pour elles ni réglage permettant de l'activer. Il en va de même pour les changements de statut de vos messages envoyés : ceux-ci sont livrés en tant qu'événements dlr, et non wa_mo.