Guide · Cybersécurité
Sécuriser un webhook : signature, anti-rejeu et traitement idempotent
Sécuriser un webhook : authentifier l’émetteur, vérifier les signatures, limiter les rejouements, valider les données et journaliser sans secrets.
Un webhook est une requête HTTP envoyée par un service vers votre application lorsqu’un événement survient. Son URL publique ne suffit pas à identifier l’émetteur : une personne qui connaît l’adresse peut tenter d’envoyer une charge utile, de rejouer un ancien événement ou de provoquer un traitement coûteux. Sécuriser un webhook demande d’authentifier la requête selon la documentation du fournisseur, de valider la charge utile, de limiter les rejouements et de rendre le traitement idempotent.
La règle fondamentale est de ne pas faire confiance à un webhook parce qu’il est bien formé. Chaque événement reste une entrée externe à vérifier avant d’écrire dans une base, d’envoyer un email ou de déclencher une action liée à un agent.
Ce que vous allez apprendre
Vous allez définir le contrat d’un endpoint, conserver le corps brut lorsqu’une signature l’exige, vérifier l’émetteur, contrôler le temps et l’identifiant d’événement, valider les champs, traiter les doublons, limiter les ressources et journaliser sans secret. Les formats exacts changent selon les fournisseurs : utilisez toujours leur documentation officielle.
Prérequis
- La documentation officielle du service émetteur.
- Un secret de signature de test distinct de la production.
- Une route HTTPS et un moyen de tester l’endpoint sans données réelles.
- Un stockage ou mécanisme pour retenir les identifiants d’événements traités.
- Une politique de traitement des erreurs et de reprise.
Ne placez pas le secret de signature dans le code, le dépôt ou une capture d’écran. Injectez-le au runtime, avec un scope et une procédure de rotation documentés.
1. Définir le contrat du webhook
Documentez le fournisseur, les types d’événement acceptés, les champs obligatoires, l’effet métier autorisé, le délai de réponse, les règles de retry et l’identité responsable. Un endpoint qui accepte n’importe quel événement puis tente de « comprendre » l’intention est plus difficile à sécuriser et à exploiter.
Séparez la réception de l’exécution longue. Un webhook doit généralement confirmer rapidement la réception après les vérifications nécessaires, puis déléguer un traitement à une file ou un worker limité. Cela réduit le risque de timeout, de retry et de blocage d’une connexion publique.
Vérification : pour chaque type d’événement, identifiez précisément la transition métier autorisée et les cas qui doivent être ignorés ou refusés.
2. Vérifier la signature sur le bon contenu
Beaucoup de fournisseurs signent le corps brut de la requête avec un secret partagé et ajoutent une signature ou un horodatage dans les en-têtes. Suivez exactement leur méthode : modifier le corps avant vérification, normaliser des espaces ou reconstruire un JSON peut invalider la signature ou créer un comportement différent de celui attendu. Utilisez une comparaison conçue pour les secrets quand la bibliothèque officielle la recommande.
Une signature valide indique que la requête a été produite par une personne possédant le secret selon le protocole ; elle ne prouve pas que l’événement est encore frais, que son type est autorisé ou que son effet métier est nouveau. Ces contrôles restent nécessaires.
Vérification : envoyez une charge de test valide, une signature modifiée et une charge altérée. Seule la première doit atteindre le traitement ; les refus ne doivent pas révéler le secret ni le détail de l’algorithme.
3. Limiter les rejouements
Utilisez l’horodatage fourni quand le protocole le prévoit et refusez les événements trop anciens ou trop éloignés de l’horloge du serveur. Conservez l’identifiant unique de l’événement dans un stockage approprié avec une durée de rétention cohérente avec les retries légitimes. Si le même identifiant revient, répondez de manière idempotente : n’exécutez pas une seconde fois une écriture, un remboursement, un email ou une action externe.
Synchronisez l’horloge du serveur et surveillez les écarts. Une politique anti-rejeu trop stricte peut bloquer des événements légitimes lors d’une panne temporaire ; une fenêtre trop large augmente l’exposition. Mesurez la distribution réelle des délais avant de fixer un seuil.
Vérification : envoyez deux fois le même événement de test. Le second appel doit produire la réponse attendue sans dupliquer l’effet métier. Testez aussi un événement expiré avec une signature de test valide.
4. Valider les données et l’autorisation métier
Après la signature, validez le type d’événement, les schémas, tailles, formats, relations et valeurs autorisées. Ne faites pas confiance à un identifiant de compte ou à une URL contenus dans la charge utile sans vérifier leur relation avec votre système. Un webhook valide peut représenter un événement que votre application ne doit pas traiter dans l’état actuel.
Appliquez les règles métier côté serveur : statut de commande attendu, utilisateur concerné, environnement, montant ou transition autorisée. Traitez les champs inconnus avec une politique explicite et conservez les versions de schéma si le fournisseur les expose. Une validation JSON seule ne suffit pas à protéger une transition métier.
Vérification : envoyez un événement bien signé mais de type non autorisé ou avec un état métier invalide. Il doit être ignoré ou refusé sans modification de données.
5. Limiter les ressources et gérer les erreurs
Appliquez des limites de taille, de débit et de temps au endpoint. Un émetteur légitime peut réessayer ; une erreur transitoire doit donc être distinguée d’une requête invalide. Retournez des statuts cohérents avec le protocole du fournisseur et documentez ceux qui déclenchent un retry. Ne répondez pas par une erreur détaillée qui révèle vos validations internes ou vos ressources.
Déplacez les traitements coûteux vers une file contrôlée. Le worker doit conserver l’idempotence et l’identité de l’événement, car une file peut elle aussi livrer un message plus d’une fois. Si le traitement échoue, journalisez le contexte minimal, préparez une reprise et évitez de transformer un retry en boucle infinie.
Vérification : soumettez une charge trop grande, un type invalide et un traitement de test qui échoue. Vérifiez que la réponse, les retries et les logs correspondent au contrat sans saturer le service.
6. Journaliser sans exposer et préparer la rotation
Journalisez l’identifiant d’événement, le type, le statut de signature, le résultat de validation, la durée et le résultat métier. Ne journalisez pas le secret de signature, l’en-tête complet d’autorisation ni la charge utile complète si elle contient des données utilisateur. Utilisez un identifiant de corrélation pour relier réception et worker sans créer une copie de contenu sensible.
Préparez la rotation du secret de signature selon les capacités du fournisseur. Une période de chevauchement peut être nécessaire ; documentez quelles versions sont acceptées et quand l’ancienne est retirée. Testez la procédure avec des secrets de test avant un incident ou une migration.
Vérification : recherchez une valeur factice secrète dans les logs et vérifiez qu’elle est absente. Réalisez une rotation de test puis confirmez le refus de l’ancien secret après la période prévue.
Problèmes fréquents et solutions
La signature échoue alors que le secret est correct. Vérifiez que le corps brut est utilisé selon le protocole, avant une transformation JSON ou middleware incompatible.
Le même événement déclenche deux actions. Ajoutez un identifiant unique persisté et rendez l’opération métier idempotente, y compris dans le worker.
Le endpoint accepte tout type d’événement. Ajoutez une allowlist et des règles métier par type ; ne déléguez pas cette décision à un modèle.
Les logs copient la charge entière. Remplacez-la par l’identifiant, le type et le statut ; conservez les détails seulement selon une politique contrôlée.
FAQ
Une URL secrète suffit-elle à protéger un webhook ?
Non. Une URL peut être devinée, copiée ou divulguée. Vérifiez une signature ou un mécanisme d’authentification documenté par l’émetteur.
Pourquoi traiter le même événement une seule fois ?
Les fournisseurs peuvent réessayer après un timeout ou une erreur. L’idempotence évite les doubles écritures et actions externes.
Peut-on accepter une requête sans signature pendant le développement ?
Utilisez plutôt un secret de test et le mécanisme officiel. Désactiver la vérification crée un comportement difficile à retirer avant production.
Faut-il stocker toutes les charges utiles ?
Non. Stockez le minimum nécessaire à l’audit et à la reprise, avec une rétention et des accès adaptés à la sensibilité des données.
Un agent IA peut-il traiter un webhook ?
Oui, mais la validation, les permissions et l’idempotence doivent être appliquées avant que la sortie de l’agent ne provoque une action.
Conclusion
Sécuriser un webhook revient à traiter chaque requête comme une entrée externe : signature sur le bon contenu, contrôle de fraîcheur, idempotence, validation métier, limites et logs minimisés. Ces contrôles empêchent un événement valide, ancien ou inattendu de devenir une action répétée ou non autorisée dans votre système.
Sources et limites
Sources primaires : OWASP Webhook Security Guidelines, OWASP Input Validation Cheat Sheet et OWASP Logging Cheat Sheet. Les en-têtes, algorithmes et comportements de retry sont propres à chaque fournisseur ; suivez sa documentation actuelle. Ce guide ne remplace pas une revue de votre endpoint et de vos règles métier.