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, 503 et 504, ainsi que les codes d'erreur d'origine Cloudflare 520 à 524 (p. ex. 521 « Web Server Is Down », que Cloudflare renvoie à la place de 502 pour 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.

TentativeDélai depuis la tentative précédente
1 (initiale)immédiatement
21 minute
35 minutes
430 minutes
560 minutes
62 heures
7 à 1410 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 :

JourSeuil (taux d'erreur rolling)Action
1supérieur à 80 %E-mail d'avertissement niveau 1
3supérieur à 85 %E-mail d'avertissement niveau 2
5supérieur à 90 %E-mail d'avertissement niveau 3
7supé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é.


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.


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"
  }
}

WhatsApp

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 :

typeEnvoyé par le client
user_fileImage, vidéo, audio, document ou sticker
locationUne position partagée
contactUne carte de contact partagée
buttonBouton de réponse rapide d'un message modèle
interactiveRéponse à un message interactif (bouton ou liste)

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"
  }
}
Dernière mise à jour: Il y a 7 heures