Webhooks expliqués : charges signées et tentatives
Vous venez d'intégrer une passerelle de paiement. La documentation indique : « Nous enverrons un webhook à votre point de terminaison lorsqu'un paiement réussit. » Vous configurez une route simple, la testez localement et déployez. Puis, en production, vous remarquez des événements en double, des notifications manquées et de mystérieuses erreurs 500. Les webhooks semblent simples—jusqu'à ce qu'ils ne le soient plus.
Ce guide explique comment les webhooks fonctionnent réellement, pourquoi les charges signées sont importantes et comment construire une logique de réessai qui maintient votre système synchronisé même lorsque les choses tournent mal.
Qu'est-ce qu'un webhook ?
Un webhook est un rappel HTTP : un système envoie une requête HTTP POST à une URL que vous fournissez lorsqu'un événement spécifique se produit. Contrairement au polling, où vous demandez répétitivement « Est-ce que quelque chose s'est passé ? », les webhooks vous poussent les événements en quasi temps réel.
Utilisations courantes :
- Les processeurs de paiement vous notifient des débits réussis.
- Les services d'hébergement Git déclenchent des pipelines CI/CD lors d'un push.
- Les plateformes de communication vous alertent de nouveaux messages.
L'expéditeur (par exemple, Stripe, GitHub) est le fournisseur de webhook ; votre serveur est le consommateur de webhook.
Pourquoi les charges signées sont essentielles
Votre point de terminaison de webhook est accessible publiquement. Quiconque connaît l'URL peut envoyer de faux événements. Sans vérification, un attaquant pourrait usurper un événement « paiement réussi » et vous pourriez expédier des marchandises gratuitement.
Les charges signées résolvent ce problème en incluant une signature cryptographique dans les en-têtes de la requête, générée avec un secret partagé connu uniquement de vous et du fournisseur. Lorsque vous recevez un webhook, vous recalculez la signature et comparez. Si elles correspondent, la charge est authentique.
Comment fonctionnent les signatures HMAC
La plupart des fournisseurs utilisent HMAC-SHA256. Le fournisseur calcule :
signature = HMAC-SHA256(secret, payload)et l'envoie dans un en-tête comme X-Signature. Votre serveur effectue le même calcul et vérifie l'égalité. Comme le secret est secret, seul le fournisseur peut générer une signature valide.
Vérifier une signature en pratique
Voici un exemple minimal en Node.js avec Express :
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const payload = req.body; // Buffer brut
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (signature !== expected) {
return res.status(401).send('Signature invalide');
}
// Traiter l'événement
const event = JSON.parse(payload);
console.log('Événement reçu :', event.type);
res.status(200).send('OK');
});
app.listen(3000);Important : Utilisez toujours le corps brut de la requête pour la vérification de signature. Si vous analysez d'abord le JSON, la chaîne sérialisée peut différer (espaces, ordre des clés) et la vérification de signature échouera.
Pièges courants
- Utiliser le mauvais secret : Certains fournisseurs ont des secrets distincts pour les modes test et live.
- Attaques temporelles : Utilisez une fonction de comparaison à temps constant (par exemple,
crypto.timingSafeEqual) pour éviter de divulguer des informations. - Ignorer les en-têtes d'horodatage : De nombreux fournisseurs incluent un horodatage pour prévenir les attaques par rejeu. Rejetez les requêtes plus anciennes que quelques minutes.
Réessais : gérer les échecs avec élégance
Les réseaux échouent, les serveurs redémarrent et les déploiements ont lieu. Les fournisseurs de webhooks s'attendent à ce que votre point de terminaison soit temporairement indisponible, ils réessaient donc les livraisons échouées. Mais les réessais introduisent de nouveaux défis : événements en double, livraison dans le désordre et boucles infinies si vous ne faites pas attention.
Comment les fournisseurs réessaient
La plupart des fournisseurs utilisent un backoff exponentiel : ils attendent un court laps de temps après le premier échec, puis des intervalles progressivement plus longs. Par exemple, Stripe réessaie jusqu'à 3 jours avec des délais croissants. GitHub réessaie jusqu'à 8 fois. Le calendrier exact varie, mais le modèle est cohérent.
Votre point de terminaison doit retourner un code de statut 2xx rapidement pour accuser réception. Tout autre statut (4xx, 5xx) signale un échec et déclenche un réessai.
Construire des gestionnaires idempotents
Parce que les réessais peuvent livrer le même événement plusieurs fois, votre gestionnaire doit être idempotent : traiter le même événement deux fois doit avoir le même effet que le traiter une fois.
Stratégies courantes :
- Suivi des ID d'événement : Stockez l'ID unique de l'événement (fourni dans la charge utile ou les en-têtes) dans une base de données. Avant de traiter, vérifiez s'il a déjà été traité.
- Upserts : Utilisez des opérations de base de données naturellement idempotentes (par exemple,
INSERT ... ON CONFLICT DO NOTHING). - Machines à états : Concevez votre logique métier pour tolérer des transitions répétées (par exemple, « marquer la commande comme payée » uniquement si ce n'est pas déjà fait).
Exemple utilisant un simple ensemble en mémoire (à titre d'illustration ; utilisez un stockage persistant en production) :
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
const event = req.body;
if (processedEvents.has(event.id)) {
return res.status(200).send('Déjà traité');
}
// Traiter l'événement...
processedEvents.add(event.id);
res.status(200).send('OK');
});Répondre rapidement
Les fournisseurs ont souvent un délai d'attente (par exemple, 5 secondes). Si votre gestionnaire effectue un traitement lourd, accusez réception immédiatement et traitez de manière asynchrone. Par exemple, poussez l'événement dans une file d'attente et retournez 200.
Meilleures pratiques pour des webhooks fiables
- Vérifiez toujours les signatures en utilisant le corps brut et une comparaison à temps constant.
- Utilisez HTTPS pour votre point de terminaison afin de prévenir l'écoute clandestine et la falsification.
- Implémentez l'idempotence pour gérer les livraisons en double.
- Journalisez tout — charges utiles entrantes, signatures et vos réponses — pour le débogage.
- Surveillez les échecs et configurez des alertes pour les erreurs répétées.
- Testez avec des événements réels en utilisant les tableaux de bord des fournisseurs ou des outils CLI.
- Gérez les événements dans le désordre en incluant des horodatages ou des numéros de séquence dans votre logique.
Comparaison : Polling vs. Webhooks
| Aspect | Polling | Webhooks |
|---|---|---|
| Latence | Dépend de l'intervalle | Quasi temps réel |
| Charge serveur | Élevée (requêtes constantes) | Faible (uniquement sur événements) |
| Complexité | Simple à implémenter | Nécessite un point de terminaison, sécurité, réessais |
| Fiabilité | Vous contrôlez les réessais | Le fournisseur contrôle les réessais |
FAQ
Qu'est-ce qu'une signature de webhook ?
Une signature de webhook est un hachage cryptographique (généralement HMAC-SHA256) de la charge utile, généré avec un secret partagé. Il permet au récepteur de vérifier que la requête provient de l'expéditeur attendu et n'a pas été falsifiée.
Comment gérer les événements de webhook en double ?
Rendez votre gestionnaire idempotent en suivant les ID d'événement ou en utilisant des opérations de base de données naturellement idempotentes. Vérifiez si un événement a déjà été traité avant d'appliquer des modifications.
Que doit retourner mon point de terminaison de webhook ?
Retournez un code de statut 2xx (par exemple, 200 OK) dès que vous avez reçu et validé l'événement. Si vous devez effectuer un traitement lourd, accusez réception immédiatement et gérez le travail de manière asynchrone.
Prêt à tester vos charges utiles de webhook ? Utilisez notre JSON Formatter pour inspecter et valider la structure JSON des événements entrants.