Guide de sécurité
Webhooks sécurisés : vérifier, protéger contre les replays, surveiller
Un endpoint de webhook est une API publique. Considérez chaque livraison comme non fiable tant que son expéditeur, sa fraîcheur, sa charge et son effet métier n’ont pas été vérifiés.
Sur cette page
Les webhooks permettent aux plateformes de paiement, fournisseurs d’identité, systèmes de commerce et services internes de notifier une application lorsqu’un événement survient. Ils évitent l’interrogation périodique, mais ils autorisent aussi un système externe à déclencher une action métier. Un WAF, un CDN ou une politique d’adresse IP peuvent réduire le trafic indésirable ; aucun ne prouve qu’une livraison est authentique. Le récepteur doit vérifier la signature documentée par le fournisseur avant d’analyser le corps, imposer une fraîcheur stricte, et rendre durablement idempotent le traitement qui en résulte.
Lorsqu’une couche d’orchestration d’edge managée se trouve devant des dizaines d’origines et d’intégrations de paiement, de commerce et de SaaS, une seule route de webhook non vérifiée suffit à laisser passer une livraison forgée. Traiter la vérification de signature, la protection contre le replay et le traitement idempotent comme une discipline unique, appliquée de la même façon chez chaque fournisseur, est ce qui permet de tenir cette échelle en sécurité plutôt que seulement rapidement.
La cohérence entre fournisseurs ne se maintient pas seule
Un seul récepteur est facile à garder correct. Une entreprise qui reçoit des webhooks de plusieurs fournisseurs derrière une même périphérie l’est moins : chacun fait évoluer son format de signature ou ses plages d’adresses IP à son propre rythme, et une régression silencieuse ressemble à du trafic normal jusqu’à ce qu’une livraison disparaisse. C’est le problème de cohérence entre fournisseurs que MYO rend visible : les taux de vérification, de fraîcheur et de doublons corrélés par route et par fournisseur en un seul endroit, plutôt que des tableaux de bord séparés que personne ne consulte avant qu’une livraison manque.
Commencez par un modèle de menace pour les webhooks
Documentez chaque endpoint avant de configurer les contrôles. Consignez son fournisseur, ses types d’événements, sa méthode d’authentification, le type de contenu et la taille de corps attendus, son comportement de nouvelle tentative, son volume de pointe et l’action métier qu’il peut déclencher. Un événement de paiement confirmé, de réinitialisation de mot de passe et de mise à jour de catalogue ne doivent pas tous recevoir la même politique.
Au minimum, prévoyez ces défaillances :
- Une requête forgée qui prétend venir d’un fournisseur de confiance.
- Un événement valide rejoué après avoir déjà été traité.
- Des livraisons dupliquées ou dans le désordre causées par des nouvelles tentatives normales.
- Un ancien événement livré après que l’état métier a changé.
- Une charge malformée, trop volumineuse ou inattendue.
- Un pic qui épuise le récepteur, la file ou une dépendance aval.
- Une rotation de secret de signature côté fournisseur que le récepteur ne sait pas absorber sans casser la vérification.
Pourquoi les webhooks sécurisés convergent vers le même schéma de signature
Les contrats de fournisseurs diffèrent dans le nom des en-têtes, mais les webhooks sécurisés convergent presque tous vers le même schéma : une signature HMAC-SHA-256 sur un horodatage et le corps brut, vérifiée par une comparaison en temps constant, acceptée seulement dans une fenêtre de fraîcheur de cinq minutes. Stripe signe horodatage + "." + charge dans Stripe-Signature ; GitHub signe seulement la charge brute dans X-Hub-Signature-256 ; la spécification ouverte Standard Webhooks (portée par Svix, entre autres) signe id + "." + horodatage + "." + charge dans webhook-signature, l’identifiant de livraison faisant aussi office de clé d’idempotence. Même forme, même tolérance de cinq minutes.
Le contrat d’Acme — horodatage + "." + corps, signé en HMAC-SHA-256 — suit cette même forme. Un code qui vérifie correctement un fournisseur, avec les noms d’en-tête et la source du secret rendus configurables, vérifie le suivant sans être réécrit.
Scénario : Acme Shop reçoit un événement de statut de paiement
Acme Shop accepte les notifications payment.updated de son prestataire de paiement sur /webhooks/payments. Le fournisseur documente un message signé composé de horodatage + "." + corps de requête brut, signé en HMAC-SHA-256 et transmis dans l’en-tête X-Acme-Signature: sha256=<hex-minuscule>. Ceci reste un exemple de contrat : utilisez toujours les en-têtes et le format de message signé exacts et documentés par votre fournisseur réel, plutôt que de les deviner par ressemblance.
Acme lit les octets une seule fois, vérifie le HMAC en temps constant, n’accepte que les horodatages situés dans une fenêtre de cinq minutes, et écrit l’identifiant d’événement dans une table de réception durable au sein de la même transaction que son enregistrement de file. Le worker réconcilie ensuite l’état du paiement à partir de l’API faisant autorité du fournisseur, car un événement peut arriver dans le désordre. Le fournisseur fait également tourner le secret de signature d’Acme tous les 90 jours, avec une fenêtre de recouvrement de 24 heures : le récepteur vérifie donc chaque livraison par rapport à l’ensemble des secrets actuellement actifs, jamais par rapport à une seule valeur codée en dur.
Vérification de signature webhook : validez les octets bruts avant l’analyse
Les fournisseurs de webhooks signent en général une représentation du corps de requête brut à l’aide d’un secret partagé ou d’une clé privée. Respectez exactement le schéma du fournisseur : les champs signés, l’encodage, le format d’horodatage et le modèle de rotation des clés font partie intégrante du contrat de sécurité, et une version « qui semble compatible » ne l’est pas forcément.
Une séquence de vérification sûre
- Exigez HTTPS et validez le certificat TLS à chaque saut réseau.
- Lisez le corps brut exactement une fois, avant que l’analyse JSON ou une normalisation des espaces ne le modifie.
- Extrayez les en-têtes de signature et d’horodatage du fournisseur.
- Recréez le message signé selon la documentation du fournisseur.
- Calculez ou vérifiez la signature avec le secret ou la clé publique actuelle.
- Vérifiez d’abord que les deux valeurs ont la même longueur, puis comparez les secrets en temps constant : une comparaison sur des tampons de longueurs différentes lève une erreur plutôt qu’un résultat exploitable.
- Rejetez les horodatages en dehors d’une courte fenêtre d’acceptation documentée.
- Analysez seulement ensuite la charge et décidez si le type d’événement est autorisé pour cet endpoint.
Certains fournisseurs envoient plusieurs signatures dans le même en-tête — une liste de paires <version>,<signature> séparées par des espaces, pour une rotation ou une migration d’algorithme. Ne parcourez que les versions implémentées et acceptez dès qu’une seule correspond ; ne laissez jamais la requête choisir l’algorithme. Vérifiez aussi Content-Length avant de lire le corps, en complément de la limite appliquée pendant la lecture.
Conservez les clés de signature dans un gestionnaire de secrets, jamais dans le code source ni dans une configuration livrée au navigateur. Une signature confirme l’intégrité du message et le détenteur de la clé ; elle n’autorise pas automatiquement tous les types d’événements. La procédure de recouvrement lors d’une rotation est détaillée ci-dessous.
Faites tourner les secrets de signature sans casser la vérification
Un secret de webhook est un identifiant de longue durée, sans expiration intégrée : faites-le tourner selon un calendrier fixe, et immédiatement après toute exposition suspectée. Une comparaison codée en dur sur un secret unique transforme la rotation en incident, car le fournisseur et le récepteur ne peuvent pas se mettre à jour de façon atomique — il existe toujours une fenêtre où des livraisons sont signées avec un secret que l’autre côté n’a pas encore reçu. Conservez donc le secret précédent valide pendant une période de recouvrement bornée (Stripe garde un secret tourné actif 24 heures ; le fournisseur d’Acme utilise la même fenêtre) et vérifiez chaque livraison par rapport à tous les secrets actuellement actifs, pas seulement le plus récent.
Chargez la liste des secrets actifs depuis le gestionnaire de secrets plutôt que depuis une seule variable d’environnement, et retirez le secret précédent une fois sa période de recouvrement terminée. Déclenchez une alerte si une livraison se vérifie encore avec l’ancien secret après cette échéance : c’est en général le signe qu’une rotation côté fournisseur ne s’est pas terminée à temps. Une cadence fixe, plutôt qu’une rotation seulement après une fuite suspectée, limite l’impact d’un secret exposé.
Empêchez les replays et les effets dupliqués
Une requête valide peut être envoyée plus d’une fois. Les fournisseurs relancent une livraison lorsqu’ils ne reçoivent pas rapidement de réponse concluante, et un attaquant qui intercepte une livraison valide peut tenter de la rejouer. Utilisez à la fois la fraîcheur et une idempotence durable.
- Fraîcheur : rejetez un ancien horodatage signé. La fenêtre doit couvrir une dérive d’horloge et un délai de transit réalistes, pas plusieurs heures ou jours par défaut.
- Suivi des replays : stockez un ID d’événement, un nonce de signature ou un ID de livraison fournisseur suffisamment longtemps pour rejeter une requête répétée durant la fenêtre de menace.
- Actions métier idempotentes : enregistrez l’ID d’événement traité et rendez les écritures aval sûres à répéter. Un événement de paiement dupliqué ne doit pas créer un envoi ou un crédit dupliqué.
- Ordonnancement : ne supposez pas que les événements arrivent dans l’ordre. Récupérez l’état courant depuis l’API faisant autorité lorsqu’un événement peut être remplacé.
Distinguez ensuite ces cas dans le traitement : un événement neuf et frais est mis en file puis traité de façon idempotente ; un identifiant déjà vu ne doit pas être remis en file ; un horodatage périmé est rejeté sans traitement ; une signature invalide n’est jamais analysée ni journalisée avec le secret ou le corps sensible ; et un échec aval après acceptation se rejoue via le worker durable, jamais en renvoyant manuellement l’événement stocké.
Utilisez une contrainte de base de données, une file durable ou un magasin transactionnel d’idempotence plutôt qu’une map de processus en mémoire. Les multiples instances applicatives et les redémarrages rendent la mémoire locale peu fiable.
Renforcez la protection contre le replay webhook avec un magasin de nonces
Une fenêtre de fraîcheur de cinq minutes borne la durée pendant laquelle une livraison capturée reste rejouable ; elle ne l’élimine pas à l’intérieur de cette fenêtre. Une requête valablement signée, interceptée sur un saut de proxy ou lors d’un incident côté fournisseur, produit toujours une signature et un horodatage valides si elle est rejouée. La table de réception d’Acme neutralise déjà ce cas, puisque l’identifiant d’événement est unique et appliqué par une contrainte de base de données avant toute écriture en file : c’est une véritable protection contre le replay webhook, pas seulement un contrôle de fraîcheur, et les deux contrôles se complètent.
Un magasin de nonces dédié, placé avant cette table de réception, se justifie dans deux cas : un contrat fournisseur sans identifiant d’événement unique (dérivez-en un en hachant le message signé, avec une durée de vie alignée sur la fenêtre de fraîcheur, via une opération atomique « définir si absent ») ; et une table de réception placée derrière une file ou un écrivain par lots, où un contrôle de nonce en amont referme la fenêtre de course où deux copies rejouées passeraient toutes deux le contrôle avant que l’une des deux ne soit validée. Ne dépassez jamais, pour la durée de vie du nonce, la longueur de la fenêtre de fraîcheur : son rôle est d’intercepter les replays intra-fenêtre, pas de devenir un second magasin d’idempotence moins durable que le premier.
Validez la charge et limitez le travail
Après vérification de l’expéditeur, traitez la charge comme une entrée non fiable. Validez son schéma, ses champs obligatoires, ses valeurs d’énumération et sa taille avant qu’elle n’atteigne un traitement coûteux. N’autorisez que les méthodes et types de contenu attendus. Rejetez un corps de requête trop volumineux avant de l’analyser.
Ne rendez pas une route de webhook largement permissive parce qu’un fournisseur doit l’appeler. Un challenge navigateur peut convenir au trafic humain suspect, mais il cassera les callbacks de machine à machine, car un fournisseur n’a aucun moyen de le résoudre. Utilisez plutôt des règles de route étroites, une limite de débit tenant compte du fournisseur, des limites de taille de corps et un vérificateur de signature explicite dans l’application.
Pour un endpoint à forte valeur, séparez la réception du traitement. Vérifiez l’événement, persistez un enregistrement durable minimal, renvoyez rapidement une réponse de succès, puis traitez-le de façon asynchrone. Cela réduit les nouvelles tentatives du fournisseur et donne à l’équipe un endroit contrôlé pour gérer les défaillances temporaires de dépendances ou les événements en dead letter.
Protégez la périphérie et l’origine
Placez l’endpoint derrière le même chemin de livraison contrôlé que le reste de l’application, sans vous fier à la périphérie — ni à l’adresse IP de l’expéditeur — comme seul contrôle : l’infrastructure de livraison d’un fournisseur peut changer, ses adresses peuvent être partagées, et un attaquant peut adresser une requête directement à l’origine.
- N’exposez que le nom d’hôte et le chemin requis.
- Autorisez les méthodes et types de contenu exacts utilisés par le fournisseur.
- Appliquez un budget de débit de requêtes et de concurrence spécifique à l’endpoint.
- Gardez l’origine privée ou refusez par défaut les accès publics directs.
- Authentifiez la connexion périphérie-origine et ne faites confiance aux en-têtes client transférés que depuis le proxy connu.
Certains fournisseurs publient les plages d’adresses IP depuis lesquelles leurs webhooks sont émis. Traitez une telle liste d’autorisation comme un signal secondaire à côté de la vérification de signature webhook, jamais comme un substitut : les fournisseurs retirent des plages avec peu de préavis, et une plage périmée doit se manifester comme son propre symptôme, pas comme une vague de livraisons apparemment forgées.
Journalisez ensemble la décision de périphérie, l’ID de requête, le résultat de signature, le résultat de fraîcheur, l’état de réception, le résultat de mise en file et le code de réponse. Masquez les signatures, en-têtes d’autorisation, cookies, corps complets, données personnelles et détails de commande.
Consultez Protection des API à la périphérie, Limitation de débit, Comment protéger votre serveur d’origine et Conception d’API fiables pour les contrôles environnants.
Testez les modes de défaillance avant la production
Utilisez un endpoint hors production et les outils de test du fournisseur, avec un secret de test issu d’une gestion de secrets approuvée, jamais une valeur de production. Préférez les outils de rejeu du fournisseur — redirection en ligne de commande, action « renvoyer » depuis un tableau de bord — à des requêtes fabriquées à la main pour les tests courants : une charge construite à la main dérive du contrat réel dès que le fournisseur le met à jour. Réservez les requêtes fabriquées à la main aux cas qu’un outil de rejeu ne peut pas produire, comme un horodatage expiré ou un secret qui vient de sortir de sa période de recouvrement.
Testez au minimum : un événement valide et signé ; le même événement rejoué (réception dupliquée détectée, aucun job supplémentaire) ; un horodatage ancien (rejet sans réception) ; un corps ou une signature altérés (rejet, aucun traitement) ; une livraison signée avec le secret précédent, dans puis après sa période de recouvrement ; la même livraison rejouée deux fois dans la fenêtre de fraîcheur ; et une panne de dépendance côté worker.
Quelques symptômes reviennent souvent en production : toutes les livraisons invalides signalent que l’infrastructure a altéré le corps avant la vérification, à corriger en restaurant la lecture du corps brut ; des événements de test faussement périmés pointent vers une horloge désynchronisée ; des effets dupliqués après une nouvelle tentative indiquent que réception et effet de bord ne sont pas liés dans une même transaction ; une page de challenge navigateur renvoyée à l’expéditeur signale une règle de WAF mal ciblée ; et un échec généralisé juste après une rotation planifiée signale que le récepteur ne vérifie que le secret le plus récent.
N’utilisez pas de secrets de production dans les journaux ou les fixtures de test. Masquez les en-têtes d’autorisation, signatures, corps contenant des données personnelles et identifiants clients. Ne conservez que les champs nécessaires à l’investigation : horodatage, ID de requête, fournisseur, endpoint, type d’événement, résultat de vérification, résultat de fraîcheur, statut de réponse et ID de corrélation sûr.
Liste de contrôle opérationnelle
Avant qu’un endpoint ne soit mis en ligne, confirmez que la vérification de signature webhook s’appuie sur la bibliothèque ou l’algorithme officiel du fournisseur, appliqué aux octets bruts ; que la protection contre le replay webhook et l’idempotence durable sont testées ; que la vérification s’exécute contre l’ensemble des secrets actuellement actifs, pas seulement le plus récent, avec une alerte si un secret retiré est encore utilisé après sa période de recouvrement ; que les types d’événements sont sur une liste d’autorisation ; que des limites de corps et de concurrence existent, y compris un contrôle de Content-Length en amont ; que les nouvelles tentatives sont comprises ; qu’un éventuel magasin de nonces a une durée de vie bornée à la fenêtre de fraîcheur et des clés cloisonnées par route ; qu’une liste d’autorisation d’adresses IP reste un signal secondaire et non une condition suffisante ; que l’accès direct à l’origine est bloqué ; et que les alertes distinguent le trafic rejeté des échecs de traitement.
Références faisant autorité
- OWASP Web Service Security Cheat Sheet
- OWASP API10: Unsafe Consumption of APIs
- GitHub: Validating webhook deliveries
- Cloudflare: Protect APIs
- Standard Webhooks specification
- Stripe: Webhook endpoints
Gardez chaque récepteur de webhook vérifié, pas seulement le premier
Échangez avec Optimi sur la cohérence de la vérification de signature webhook, de la protection contre le replay et de la visibilité MYO — le socle Sécurité et Visibilité de la Suite Optimi — sur l’ensemble de vos fournisseurs et de vos routes.
Évaluer la sécurité des webhooks