Webhooks expliqués : signatures et réessais
Vous venez d'intégrer un fournisseur de paiement tiers. L'utilisateur effectue un achat, mais votre base de données n'est jamais mise à jour. Pourquoi ? Parce que le webhook censé notifier votre application a échoué silencieusement. Sans vérification de signature et logique de réessai appropriées, les webhooks peuvent devenir une source de bugs mystérieux et de failles de sécurité.
Dans cet article, nous expliquons comment fonctionnent les webhooks, pourquoi vous devez vérifier les payloads signés et comment concevoir un mécanisme de réessai robuste. Nous vous montrons également comment tester et déboguer efficacement les webhooks.
Que sont les webhooks ?
Les webhooks sont des callbacks HTTP définis par l'utilisateur. Ils permettent à un système d'envoyer des données en temps réel à un autre lorsqu'un événement se produit. Par exemple, lorsqu'un paiement est traité, le fournisseur de paiement envoie une requête HTTP POST à une URL que vous spécifiez, contenant les détails de l'événement.
Contrairement au polling, où votre application demande régulièrement des mises à jour, les webhooks vous poussent les données dès qu'elles sont disponibles. Cela les rend efficaces et quasi temps réel.
Le problème de sécurité : pourquoi vérifier les signatures ?
Les points de terminaison de webhook sont des URL accessibles publiquement. N'importe qui peut leur envoyer une requête POST. Sans vérification, un attaquant pourrait forger une requête pour, par exemple, marquer une commande comme payée sans paiement réel. Pour éviter cela, les fournisseurs signent chaque payload avec une clé secrète.
Comment fonctionnent les signatures HMAC
La plupart des fournisseurs utilisent HMAC (Hash-based Message Authentication Code) avec SHA-256. Ils calculent un hash du corps de la requête à l'aide d'un secret partagé et l'incluent dans un en-tête, comme X-Signature. Votre serveur recalcule le hash et le compare à l'en-tête. S'ils correspondent, la requête est authentique.
Voici un exemple simple en Python :
import hmac
import hashlib
def verify_signature(payload, secret, signature):
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Utilisez toujours une fonction de comparaison à temps constant comme hmac.compare_digest pour éviter les attaques temporelles.
Erreurs courantes à éviter
- Utiliser le mauvais secret : Chaque environnement (test, production) doit avoir son propre secret.
- Ne pas vérifier l'horodatage : Certains fournisseurs incluent un horodatage pour prévenir les attaques par rejeu. Rejetez les requêtes plus anciennes que quelques minutes.
- Journaliser les secrets : Ne journalisez jamais le secret ou la signature complète en production.
Concevoir des réessais fiables
Les webhooks sont envoyés sur Internet, les échecs sont donc inévitables. Les réseaux tombent, les serveurs redémarrent et les points de terminaison expirent. Un bon système de webhook réessaie les livraisons échouées avec un backoff exponentiel.
Comment fonctionnent généralement les réessais
Les fournisseurs réessaient généralement avec des délais croissants : après 1 minute, 5 minutes, 30 minutes, etc., jusqu'à un nombre maximum de tentatives (par exemple 5-10). Si tous les réessais échouent, l'événement est marqué comme échoué et peut être disponible pour un rejeu manuel.
Votre point de terminaison doit répondre rapidement avec un code de statut 2xx pour accuser réception. Si vous renvoyez un code non-2xx ou si vous expirez, le fournisseur réessaiera.
Implémenter l'idempotence
Parce que les réessais peuvent provoquer des livraisons en double, votre point de terminaison doit être idempotent. Utilisez l'ID d'événement (inclus dans le payload ou les en-têtes) pour suivre les événements traités. Avant de traiter, vérifiez si vous avez déjà géré cet ID. Stockez les ID traités dans une base de données avec une TTL.
Exemple de vérification d'idempotence :
def process_event(event_id, data):
if db.exists(f"processed:{event_id}"):
return # déjà traité
# traiter les données
db.setex(f"processed:{event_id}", 86400, "1") # expire après 24h
Construire un récepteur de webhook : étape par étape
- Exposez un point de terminaison HTTPS : Utilisez TLS pour protéger les données en transit.
- Vérifiez la signature : Rejetez les requêtes avec des signatures invalides avec un 401.
- Vérifiez l'horodatage : S'il est fourni, assurez-vous qu'il est dans une fenêtre acceptable (par exemple 5 minutes).
- Analysez le payload : Validez la structure JSON et les champs requis.
- Vérifiez l'idempotence : Utilisez l'ID d'événement pour éviter les doublons.
- Traitez de manière asynchrone : Répondez immédiatement avec 200, puis traitez dans une tâche en arrière-plan pour éviter les expirations.
- Journalisez et surveillez : Enregistrez les échecs et configurez des alertes.
Tester les webhooks localement
Pendant le développement, vous avez besoin d'un moyen de recevoir des webhooks sur votre machine locale. Des outils comme ngrok ou localtunnel créent une URL publique qui redirige vers votre localhost. Alternativement, de nombreux fournisseurs offrent une CLI pour déclencher des événements de test.
Testez toujours la vérification de signature avec des signatures valides et invalides. Simulez des réessais en renvoyant des codes d'erreur et en observant le comportement de réessai du fournisseur.
Comparaison : Webhooks vs Polling
| Aspect | Webhooks | Polling |
|---|---|---|
| Temps réel | Quasi temps réel | Retardé par l'intervalle |
| Efficacité | Envoie des données uniquement lors d'un événement | Requêtes fréquentes même sans données |
| Complexité | Nécessite un point de terminaison, vérification de signature, réessais | Plus simple à implémenter |
| Fiabilité | Dépend du mécanisme de réessai | Vous contrôlez la planification |
FAQ
Qu'est-ce qu'un payload de webhook signé ?
Un payload signé inclut une signature cryptographique (généralement HMAC) qui permet au récepteur de vérifier que la requête provient de l'expéditeur attendu et n'a pas été altérée.
Combien de fois un webhook doit-il être réessayé ?
La plupart des fournisseurs réessaient 5 à 10 fois avec un backoff exponentiel. Le nombre exact dépend de la politique du fournisseur. Votre point de terminaison doit être idempotent pour gérer les doublons.
Quel code de statut HTTP mon webhook doit-il renvoyer ?
Renvoyez un code de statut 2xx (par exemple 200 OK) pour accuser réception avec succès. Tout autre code (4xx ou 5xx) signale un échec et déclenche des réessais.
Prêt à tester vos payloads de webhook ? Utilisez notre JSON Formatter pour valider et embellir rapidement les payloads JSON.