Fiabilité des webhooks sur un site autonome : mécanismes de nouvelle tentative, idempotence, vérification de signature et supervision
1. Pourquoi un webhook est par nature peu fiable
Un webhook, c’est le système du partenaire qui envoie activement un événement en POST à votre URL de rappel. Il repose sur HTTP, qui est sans connexion et ne garantit pas la livraison : une coupure réseau, un redémarrage de votre service, un délai de traitement dépassé, et cette livraison peut se perdre. Plus gênant encore : impossible de savoir si le destinataire l’a reçue. Vous avez envoyé, mais sans réponse 2xx : le réseau a-t-il perdu le message, ou le destinataire l’a-t-il traité mais perdu la réponse ?
Le jugement contre-intuitif est ici :Un système de webhooks réessaie par défaut. Votre récepteur doit donc être conçu pour des livraisons répétées, et non partir du principe qu’elles n’arriveront qu’une fois. La pratique standard des plateformes matures comme Stripe ou Shopify : tant que le récepteur ne renvoie pas de 2xx, renvoyer avec un backoff exponentiel, jusqu’à 5 à 8 fois sur environ 24 heures.
Sans idempotence, le même événement de paiement sera comptabilisé plusieurs fois. Pour la liaison des prospects au système, voir Des leads du site au CRM, où les événements en double se posent aussi.
Il suffit qu’un des quatre éléments manque pour perdre un cran de fiabilité. Le tableau ci-dessous détaille, pour chaque élément, ce qu’il fait et ce qui se passe si on l’oublie.
| Élément | Rôle | Pratique standard | Conséquence si oublié |
|---|---|---|---|
| Vérification de signature | Confirmer que la requête vient bien du partenaire | Contrôle de l’en-tête HMAC-SHA256 | Injection d’événements falsifiés et de fausses commandes |
| Accusé de réception rapide | Éviter le renvoi pour délai dépassé | Répondre 2xx d’abord, traiter ensuite en asynchrone | Traitement lent provoquant des livraisons en double |
| Déduplication idempotente | Ne traiter un événement en double qu’une seule fois | Enregistrer l’identifiant d’événement en base pour dédupliquer | Même commande comptabilisée plusieurs fois |
| Nouvelle tentative avec backoff | Une défaillance temporaire finit par être livrée | Backoff exponentiel avec jitter | Une panne ponctuelle fait perdre la commande |
| Supervision de la file de messages morts | Aucun événement en échec perdu en silence | Au-delà de la limite, alerte sur la file de messages morts | Des commandes perdues durablement sans que personne ne le sache |
2. Vérification de signature : confirmer que cette requête n’a pas été falsifiée par un tiers
L’URL de callback du Webhook est une URL accessible publiquement : n’importe qui peut y envoyer des données en POST. Sans vérification de signature, un attaquant peut forger un faux événement « paiement réussi » et votre backend risque réellement d’accorder des droits ou de créer une commande. Ce n’est pas un risque théorique, c’est une surface d’attaque déjà exploitée.
La méthode standard est HMAC-SHA256 : l’émetteur signe le corps de la requête avec une clé convenue et place la signature dans un en-tête (par exemple X-Signature) ; à la réception, vous recalculez la signature sur le corps brut avec la même clé, vous comparez les deux, et vous ne traitez que si elles correspondent.
Deux détails sont indispensables : premièrement, comparez avec une fonction de comparaison à temps constant, pas avec un simple == de chaînes, pour éviter les attaques temporelles ; deuxièmement, vérifiez l’horodatage dans l’en-tête et rejetez toute requête dont l’écart dépasse 5 minutes, pour empêcher la relecture d’anciennes requêtes. En cas de fuite de clé, la rotation doit être immédiate.
| Point de contrôle | Mise en œuvre | Traitement en cas d’échec |
|---|---|---|
| Signature correspondante | Recalcul HMAC-SHA256 et comparaison | Rejet direct en 401 |
| Horodatage récent | Écart avec l’heure serveur ≤ 5 minutes | Rejet, anti-relecture |
| Clé expirée | Rotation avec ancienne et nouvelle clés en parallèle | Nouvel essai avec l’ancienne clé |
| Corps de requête modifié | Signature calculée sur les octets bruts | Ne pas décoder le JSON avant de signer |
3. Accusé de réception rapide et traitement asynchrone : ne faites pas le gros du travail dans la requête
C’est le piège le plus courant. Beaucoup de gens, à la réception d’un Webhook, enchaînent dans la requête synchrone une série de tâches lourdes : requêtes en base, envoi d’e-mails, appels à des API tierces, écriture de logs — le tout prend 5 secondes. En face, après 3 secondes sans 2xx, le délai est considéré comme dépassé et le renvoi commence : vous êtes encore en train de traiter le premier événement que le deuxième arrive déjà.
Le bon modèle est en trois temps : à la réception, vérifiez la signature (en millisecondes), puis enregistrez l’événement tel quel en base et renvoyez immédiatement un 2xx, et laissez une tâche asynchrone en arrière-plan traiter le reste tranquillement. Ainsi, l’émetteur obtient une confirmation en moins de 3 secondes et ne renvoie pas ; le travail lourd peut prendre son temps, l’accusé de réception est déjà parti. Les événements en échec restent dans la file et la tâche asynchrone les réessaie d’elle-même.
Exemple concret : Un événement de paiement réussi arrive. Le récepteur ne fait que trois choses : vérifier la signature, écrire l’ID de l’événement et le payload brut dans une table events, et renvoyer immédiatement un 200. Le vrai travail — « ajouter le stock, envoyer l’e-mail, notifier l’ERP » — est fait tranquillement par un worker en arrière-plan qui extrait les événements à traiter de la table events. Même si l’envoi de l’e-mail prend 10 secondes, cela n’affecte pas le renvoi côté émetteur. Pour la même logique de fiabilité appliquée à l’envoi de formulaires, voir Fiabilité du formulaire de demande de devis.
| Phase | Action | Objectif de durée | En cas d’échec |
|---|---|---|---|
| Réception synchrone | Vérification de signature + enregistrement + 2xx | Moins de 300 millisecondes | Rejet direct si la signature échoue |
| Traitement asynchrone | Requêtes en base, notifications, appels en aval | De quelques secondes à quelques minutes | En cas d’échec, reste dans la file pour nouvel essai |
| Nouvel essai | Relance selon un intervalle de backoff | Backoff exponentiel | Au-delà de la limite, file de lettres mortes |
| Reprise manuelle | Déclenchement manuel depuis la file de lettres mortes | Inspection quotidienne | Consigner la raison de la reprise |
4. Idempotence et nouveaux essais : le même événement dix fois ne doit être traité qu’une fois
L’idempotence, c’est simple : exécuter une même opération une fois ou dix fois donne exactement le même résultat. Un webhook doit être idempotent par nature, car vous ne pouvez pas empêcher l’émetteur de renvoyer un événement. La mise en œuvre est simple : chaque événement porte un ID unique global ; avant de le traiter, vous consultez la table events et, si l’ID est déjà présent, vous renvoyez un succès et sautez le traitement.
Pour les tentatives, utilisez un backoff exponentiel avec jitter : 1 minute après le premier échec, 2 minutes après le deuxième, 4 minutes après le troisième, en doublant à chaque fois, jusqu’à 5 à 8 tentatives sur environ 24 heures. Le jitter aléatoire évite que tous les événements en échec soient relancés à la même seconde et ne fassent retomber votre service (c’est la tempête de retries). Les événements qui dépassent la limite ne sont pas perdus : ils partent en file de lettres mortes, en attente d’une intervention.
| Nombre de tentatives | Intervalle de backoff (environ) | Temps cumulé | Explication |
|---|---|---|---|
| 1re tentative | 1 minute | 1 minute | Jitter réseau temporaire |
| 2e tentative | 2 minutes | 3 minutes | Redémarrage bref du service |
| 3e tentative | 4 minutes | 7 minutes | Indisponibilité temporaire en aval |
| 4e tentative | 8 minutes | 15 minutes | Panne prolongée |
| 5e à 8e tentative | 15 à 30 minutes | Environ 24 heures | Échec persistant : file de lettres mortes |
V. Surveillance et lettres mortes : ne laissez pas les commandes perdues passer inaperçues
La panne la plus sournoise d’un webhook, c’est la commande perdue en silence : l’émetteur croit avoir réussi, vous n’avez aucune trace, et des semaines plus tard un client se plaint de ne pas avoir été livré alors qu’il a payé. Vous découvrez alors que l’événement n’est jamais arrivé. La surveillance doit donc porter sur trois points : le taux de livraison, l’accumulation dans la file de tentatives et le nombre d’éléments en file de lettres mortes.
Fixez des seuils chiffrés : alertez sur votre canal habituel dès que la file de tentatives dépasse 100 éléments ou qu’aucune livraison n’a réussi pendant 30 minutes consécutives. Inspectez la file de lettres mortes une fois par jour et rattrapez manuellement les commandes en attente. Un taux de livraison inférieur à 99 % mérite un examen : en bonne santé, un webhook doit approcher 99,9 % ; en dessous de 99 %, un problème persiste. Pour mettre en place une surveillance et des alertes générales, voir Surveillance et alertes du site.
| Indicateur surveillé | Valeur saine | Seuil d’alerte | Action |
|---|---|---|---|
| Taux de livraison | ≥ 99,9 % | < 99 % pendant 1 heure consécutive | Consultez les logs du récepteur |
| Accumulation dans la file de tentatives | proche de 0 | Plus de 100 éléments | Vérifiez si l’aval est bloqué |
| File de lettres mortes | 0 | Toute accumulation | Rattrapez manuellement et identifiez la cause |
| Aucune livraison réussie | Des événements en continu | 0 pendant 30 minutes | Vérifiez si l’URL de callback est bloquée |
| Échecs de vérification de signature | proche de 0 | Multiplication par 10 | Quelqu’un scanne peut-être l’interface |
FAQ — Questions fréquentes
Pourquoi mon webhook reçoit-il parfois des événements de commande en double ?
Faut-il traiter un webhook dès sa réception ou répondre d’abord ?
Est-ce qu’on peut se passer de la vérification de signature, puisque tout passe en HTTPS ?
Comment définir l’intervalle entre les tentatives, et pourquoi ne pas réessayer toutes les minutes à intervalle fixe ?
Que faire des événements qui échouent encore après de nombreuses tentatives ?
Comment savoir si un Webhook perd des événements en silence ?
Pour conclure
Le principe fondamental de la fiabilité d’un Webhook, c’est qu’il ne s’agit pas d’un envoi unidirectionnel, mais d’un protocole bidirectionnel où l’on renvoie tant qu’il n’y a pas d’accusé de réception. Quand vous concevez le récepteur, vous devez considérer comme hypothèses par défaut la livraison en double, la falsification et la perte, et non comme des cas improbables. Parmi les quatre éléments, l’idempotence avec déduplication et l’accusé de réception rapide sont les deux plus souvent négligés et ceux qui causent le plus d’incidents : commencez par bien faire ces deux-là, et le taux d’incidents chute de plus de la moitié.
Trois étapes pour passer en production. Premièrement, côté réception, contentez-vous de vérifier la signature, d’enregistrer en base et de renvoyer un 2xx en moins de 3 secondes : tout le traitement métier passe en asynchrone. Deuxièmement, créez une table de déduplication basée sur l’ID d’événement, et gérez les retries avec un backoff exponentiel et du jitter. Troisièmement, inspectez la file de messages morts chaque jour, et branchez des alertes sur le taux de livraison et le volume en attente. Une fois ces trois étapes en place, votre chaîne de callbacks passe de « on perd des commandes de temps en temps, on verra bien » à « si on en perd, on le sait le jour même et on peut les récupérer ».
Une phrase à retenir : la fiabilité d’un Webhook ne dépend pas de la régularité de l’émetteur, mais de la simplicité de votre réception — vérifier la signature, répondre vite, dédupliquer. Mieux vaut traiter deux fois que manquer une commande.
