tadata
← Retour à l'accueil

Des webhooks bien faits : signatures, nouvelles tentatives, idempotence et la spécification Standard Webhooks

#architecture#api#security#best-practices#reliability

Tout produit SaaS finit par envoyer des webhooks, et presque chaque première implémentation a les mêmes quatre défauts : le récepteur ne peut pas vérifier qui a envoyé le message, une nouvelle tentative crée une commande en double, un récepteur lent bloque l'émetteur, et personne ne sait si l'événement 4821 a jamais été livré. La spécification Standard Webhooks (version 1.0.2, février 2026), adoptée par des fournisseurs de paiement, de messagerie et d'identité, règle le premier point par convention ; les autres sont des choix de conception des deux côtés. Cet article suit le cycle de vie d'un événement, les en-têtes et la procédure de vérification, le calendrier de nouvelles tentatives qui fonctionne, et les modes de défaillance à tester avant qu'un client ne les trouve.

Un événement, de la file à la livraison

tadata
En file Envoi Livré Nouvelle tentativeplanifiée Lettre morte Point d'entréedésactivé un travailleur leprend 2xx en moins de10 s délai dépassé,5xx, 429 ouerreur deconnexion délai écoulé,nouveauxhorodatage etsignature tentativesépuisées (environ3 jours) 4xx autre que 408 et429 : pas de nouvelletentative taux d'échecau-dessus duseuil pendant 3jours le client rejouedepuis le tableaude bord Cycle de vie d'une livraison de webhook côté émetteur
En file Envoi Livré Nouvelle tentativeplanifiée Lettre morte Point d'entréedésactivé un travailleur leprend 2xx en moins de10 s délai dépassé,5xx, 429 ouerreur deconnexion délai écoulé,nouveauxhorodatage etsignature tentativesépuisées (environ3 jours) 4xx autre que 408 et429 : pas de nouvelletentative taux d'échecau-dessus duseuil pendant 3jours le client rejouedepuis le tableaude bord Cycle de vie d'une livraison de webhook côté émetteur

Deux transitions portent l'essentiel de la conception. Un 4xx est définitif : le récepteur a rejeté le message, et renvoyer les mêmes octets ne le fera pas changer d'avis, sauf 408 et 429 qui veulent dire « plus tard ». Et un événement en lettre morte est rejouable par le client : le tableau de bord qui liste les livraisons échouées et propose un bouton de relecture est la fonction qui transforme un ticket de support en libre-service.

Ce qui passe sur le fil

tadata
POST https://client.example/hooks/orders POST /hooks/orders HTTP/1.1 Content-Type: application/json webhook-id : msg_2Y7bq9Kp3mN 1 webhook-timestamp : 1760875200 2 webhook-signature: v1, K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= 3 {"type":"order.paid","id":"evt_4821","data":{"order":"o_1187","amount":4900,"currency":"EUR"} } 200 OK HTTP/1.1 200 OK 4 Content-Length: 0 1 unique par événement, identique à chaque nouvelle tentative : la clé d'idempotence 2 secondes Unix ; rejeter au-delà de 5 minutes d'écart 3 HMAC-SHA256 de « id.horodatage.corps » avec le secret du point d'entrée, en base64 ; plusieurs signatures possiblespendant une rotation 4 répondre vite avec un corps vide ; faire le travail après mise en file En-têtes Standard Webhooks sur la requête, réponse du récepteur
POST https://client.example/hooks/orders POST /hooks/orders HTTP/1.1 Content-Type: application/json webhook-id : msg_2Y7bq9Kp3mN 1 webhook-timestamp : 1760875200 2 webhook-signature: v1, K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= 3 {"type":"order.paid","id":"evt_4821","data":{"order":"o_1187","amount":4900,"currency":"EUR"} } 200 OK HTTP/1.1 200 OK 4 Content-Length: 0 1 unique par événement, identique à chaque nouvelle tentative : la clé d'idempotence 2 secondes Unix ; rejeter au-delà de 5 minutes d'écart 3 HMAC-SHA256 de « id.horodatage.corps » avec le secret du point d'entrée, en base64 ; plusieurs signatures possiblespendant une rotation 4 répondre vite avec un corps vide ; faire le travail après mise en file En-têtes Standard Webhooks sur la requête, réponse du récepteur

La chaîne signée est webhook-id.webhook-timestamp.corps brut. Signer l'horodatage défait le rejeu ; signer les octets bruts impose au récepteur de vérifier avant d'analyser le JSON, parce qu'une resérialisation change les espaces et l'ordre des clés. Stripe, les fournisseurs fondés sur Svix et les bibliothèques de référence de la spécification utilisent tous une tolérance de cinq minutes.

La liste de contrôle du récepteur, dans l'ordre

tadata
# Action Expected result Notes 1 Lire les octets bruts du corpsavant toute analyse JSON Exactement les octets signés parl'émetteur Les cadriciels quianalysent tôt doiventexposer le corps brut 2 Comparer webhook-timestamp àl'horloge Moins de 5 minutes d'écart Plus vieux : rejeu oudérive d'horloge ;rejeter en 400 3 Calculer HMAC-SHA256(secret,id.horodatage.corps) et compareren temps constant Correspond à l'une dessignatures v1 Essayer chaque secretpendant une rotation 4 Chercher webhook-id dans la tabledes traités Jamais vu Déjà vu : répondre 200tout de suite, ne rienfaire 5 Mettre l'événement en file etrépondre 200 Réponse bien avant le délai del'émetteur Le traitement se faitdans un travailleur, pasdans la requête 6 Traiter dans une transaction quiinsère aussi webhook-id dans latable des traités Effet et reçu validés ensemble Un plantage entre lesdeux ne peut pas créerde doublon 7 En cas d'échec du traitement,l'enregistrer pour le tableau debord Visible par les opérateurs Ne jamais le signaler enrenvoyant 5xx :l'émetteur renverrait unmessage déjà accepté Vérifier et traiter une livraison
# Action Expected result Notes 1 Lire les octets bruts du corpsavant toute analyse JSON Exactement les octets signés parl'émetteur Les cadriciels quianalysent tôt doiventexposer le corps brut 2 Comparer webhook-timestamp àl'horloge Moins de 5 minutes d'écart Plus vieux : rejeu oudérive d'horloge ;rejeter en 400 3 Calculer HMAC-SHA256(secret,id.horodatage.corps) et compareren temps constant Correspond à l'une dessignatures v1 Essayer chaque secretpendant une rotation 4 Chercher webhook-id dans la tabledes traités Jamais vu Déjà vu : répondre 200tout de suite, ne rienfaire 5 Mettre l'événement en file etrépondre 200 Réponse bien avant le délai del'émetteur Le traitement se faitdans un travailleur, pasdans la requête 6 Traiter dans une transaction quiinsère aussi webhook-id dans latable des traités Effet et reçu validés ensemble Un plantage entre lesdeux ne peut pas créerde doublon 7 En cas d'échec du traitement,l'enregistrer pour le tableau debord Visible par les opérateurs Ne jamais le signaler enrenvoyant 5xx :l'émetteur renverrait unmessage déjà accepté Vérifier et traiter une livraison

Comment espacer les nouvelles tentatives

tadata
0 min 500 min 1000 min 1500 min 2000 min 2 4 6 8 10 minutes de délai Calendrier de recul (minutes) Même calendrier avec ±20 % de gigue (un tirage) 30 min : la plupart des pannes sont finies quotidien jusqu'au jour 3 tentative Délai avant chaque tentative : recul exponentiel avec gigue, plafonné, sur environ trois jours
0 min 500 min 1000 min 1500 min 2000 min 2 4 6 8 10 minutes de délai Calendrier de recul (minutes) Même calendrier avec ±20 % de gigue (un tirage) 30 min : la plupart des pannes sont finies quotidien jusqu'au jour 3 tentative Délai avant chaque tentative : recul exponentiel avec gigue, plafonné, sur environ trois jours

Les chiffres exacts comptent moins que trois propriétés : la première nouvelle tentative vient vite (un redémarrage de déploiement dure quelques secondes), les délais grandissent jusqu'à devenir quotidiens, et la gigue étale les tentatives pour que dix mille événements ne frappent pas un point d'entrée convalescent à la même seconde. Stripe réessaie jusqu'à trois jours ; les fournisseurs fondés sur Svix utilisent un calendrier fixe de huit tentatives sur environ un jour ; GitHub ne réessaie pas automatiquement, c'est pourquoi ses clients construisent leur propre relecture. Arrêtez sur un 4xx, et désactivez un point d'entrée qui échoue à chaque livraison depuis des jours, après avoir écrit à son propriétaire.

Pourquoi l'idempotence est l'affaire du récepteur

tadata
Déjà vu ? \ Corps Même corps qu'avant Corps différent Total webhook-id nouveau 0 0 0 webhook-id déjà traité 0 0 0 Total 0 0 1 Ce que voit le récepteur pour un webhook-id et un corps donnés
Déjà vu ? \ Corps Même corps qu'avant Corps différent Total webhook-id nouveau 0 0 0 webhook-id déjà traité 0 0 0 Total 0 0 1 Ce que voit le récepteur pour un webhook-id et un corps donnés

La livraison au moins une fois est la seule garantie qu'un émetteur peut honnêtement donner sur HTTP : une réponse perdue après que le récepteur a traité l'événement ressemble exactement à un délai dépassé. Les doublons sont donc normaux, et le récepteur doit rendre inoffensif le traitement du même webhook-id deux fois. La table des reçus indexée par l'id, écrite dans la même transaction que l'effet, est tout le mécanisme ; garder les ids pendant toute la fenêtre de nouvelles tentatives de l'émetteur (trois jours) plutôt que quelques minutes ferme la dernière brèche.

Modes de défaillance à tester avant de livrer

tadata
# Item Failure mode Effect Cause S O D RPN Action After 5 Récepteur Dépend de l'ordre desévénements Un remboursementtraité avant sonpaiement Suppose l'ordre delivraison 8 5 7 280 Agir sur l'état courantde l'objet relu par id,pas sur la charge del'événement 112 6 Émetteur Pas de tableau debord des livraisons Le support ne peutpas répondre à «l'avez-vous envoyé ?» Livraisons nonpersistées 5 7 8 280 Conserver tentatives,codes et corps 30 jours; bouton de relecture 15 1 Récepteur Traite dans larequête et dépasse ledélai L'émetteur réessaie ;travail fait deuxfois Traitementsynchrone 7 8 4 224 Mettre en file puis 200 56 3 Émetteur Réessaie les réponses4xx sans fin La file grossit ; lerécepteur est inondé Nouvelle tentativesur tout non-2xx 6 6 5 180 Réessayer seulement sur408, 429, 5xx et erreursréseau 30 4 Les deux La rotation du secretcasse la vérificationpendant des heures Événements en lettremorte Un seul secret àla fois 7 4 6 168 Deux secrets actifs ;l'en-tête de signatureliste les deux 42 2 Récepteur Vérifie la signaturesur du JSONresérialisé Toutes les livraisonsrejetées après unemise à jour debibliothèque Le cadricielanalyse le corpsd'abord 8 5 3 120 Intergiciel de corpsbrut ; un test avec unvecteur connu 24 6 failure modes · highest RPN 280 · 3 at or above 200 · total RPN 1252 → 279 after actions (−78 %) RPN ≥ 200 RPN ≥ 100 or S ≥ 9 lower RPN = S × O × D (1-10 each) AMDEC d'une intégration de webhooks (gravité, occurrence, détectabilité de 1 à10)
# Item Failure mode Effect Cause S O D RPN Action After 5 Récepteur Dépend de l'ordre desévénements Un remboursementtraité avant sonpaiement Suppose l'ordre delivraison 8 5 7 280 Agir sur l'état courantde l'objet relu par id,pas sur la charge del'événement 112 6 Émetteur Pas de tableau debord des livraisons Le support ne peutpas répondre à «l'avez-vous envoyé ?» Livraisons nonpersistées 5 7 8 280 Conserver tentatives,codes et corps 30 jours; bouton de relecture 15 1 Récepteur Traite dans larequête et dépasse ledélai L'émetteur réessaie ;travail fait deuxfois Traitementsynchrone 7 8 4 224 Mettre en file puis 200 56 3 Émetteur Réessaie les réponses4xx sans fin La file grossit ; lerécepteur est inondé Nouvelle tentativesur tout non-2xx 6 6 5 180 Réessayer seulement sur408, 429, 5xx et erreursréseau 30 4 Les deux La rotation du secretcasse la vérificationpendant des heures Événements en lettremorte Un seul secret àla fois 7 4 6 168 Deux secrets actifs ;l'en-tête de signatureliste les deux 42 2 Récepteur Vérifie la signaturesur du JSONresérialisé Toutes les livraisonsrejetées après unemise à jour debibliothèque Le cadricielanalyse le corpsd'abord 8 5 3 120 Intergiciel de corpsbrut ; un test avec unvecteur connu 24 6 failure modes · highest RPN 280 · 3 at or above 200 · total RPN 1252 → 279 after actions (−78 %) RPN ≥ 200 RPN ≥ 100 or S ≥ 9 lower RPN = S × O × D (1-10 each) AMDEC d'une intégration de webhooks (gravité, occurrence, détectabilité de 1 à10)

La ligne sur l'ordre mérite un mot. Les webhooks arrivent dans le désordre sous les nouvelles tentatives, et réordonner côté récepteur est un jeu perdu ; le schéma robuste consiste à traiter le webhook comme une notification qu'une chose a changé et à relire l'état courant de l'objet dans l'API par son id, de sorte qu'un événement périmé ne puisse rien faire régresser.

En résumé

Un webhook est une petite requête HTTP au grand rayon d'action : elle traverse deux organisations, deux cycles de déploiement et deux notions de « livré ». Adoptez les en-têtes Standard Webhooks pour que chaque récepteur puisse utiliser un vérificateur sur étagère, réessayez avec recul et gigue pendant des jours et arrêtez sur un 4xx, rendez le récepteur idempotent avec une table de reçus, et traitez les événements comme des invitations à relire l'état. Puis construisez le tableau de bord, parce que la première question du support sur les webhooks est toujours « l'avez-vous envoyé ? ». Nos articles sur la conception d'API et la modélisation des menaces couvrent l'interface au sens large et ses risques.

Sources : spécification Standard Webhooks v1.0.2 (février 2026) et sa liste de contrôle du récepteur ; documentation de Stripe, Svix et GitHub sur les nouvelles tentatives et les signatures ; missions. Lus le 19 octobre 2026.

Nous utilisons des cookies analytiques pour améliorer votre expérience. Aucune donnée personnelle n'est collectée.