Webhooks expliqués : signatures et nouvelles tentatives
Vous avez intégré une passerelle de paiement ou un service CI/CD, et vous devez maintenant recevoir des événements en temps réel. Les webhooks sont la solution standard, mais ils présentent deux défis majeurs : vérifier que la requête provient bien de l'expéditeur attendu et gérer les échecs lorsque votre point de terminaison est indisponible ou lent. Cet article explique comment implémenter des charges utiles de webhook signées et une logique de nouvelle tentative robuste, avec des exemples de code pratiques.
Que sont les webhooks ?
Un webhook est un rappel HTTP défini par l'utilisateur. Au lieu que votre application interroge une API pour obtenir des mises à jour, le fournisseur envoie une requête HTTP POST à une URL que vous spécifiez chaque fois qu'un événement se produit. Cela rend les intégrations efficaces et quasi temps réel.
Cependant, les webhooks sont basés sur le push, ce qui signifie que votre point de terminaison doit être accessible publiquement, gérer une charge inattendue et faire face aux requêtes en double ou malveillantes. C'est là que la signature et les nouvelles tentatives entrent en jeu.
Pourquoi signer les charges utiles des webhooks ?
Sans vérification, toute personne connaissant l'URL de votre webhook pourrait envoyer de faux événements. La signature des charges utiles avec un secret partagé vous permet d'authentifier l'expéditeur et de garantir que la charge utile n'a pas été falsifiée.
L'approche la plus courante est HMAC (Hash-based Message Authentication Code) avec SHA-256. Le fournisseur calcule une signature sur le corps brut de la requête à l'aide d'une clé secrète, et l'inclut dans un en-tête (par exemple, X-Signature). Votre serveur recalcule la signature et les compare.
Implémentation de la vérification HMAC
Voici un exemple simple en Node.js avec Express :
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body for signature verification
app.use(express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const payload = req.body; // Buffer
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = JSON.parse(payload);
console.log('Received event:', event.type);
res.status(200).send('OK');
});
app.listen(3000, () => console.log('Webhook server running'));
Points clés :
- Utilisez le corps brut : Analysez le corps en tant qu'octets bruts, et non en JSON, car toute transformation modifie la signature.
- Comparaison à temps constant : Utilisez
crypto.timingSafeEqualpour éviter les attaques temporelles. - Gestion des secrets : Stockez les secrets de manière sécurisée (variables d'environnement, gestionnaires de secrets).
Gérer les nouvelles tentatives : rendre les webhooks fiables
Les réseaux échouent, les serveurs plantent et les déploiements provoquent des temps d'arrêt. Un système de webhook fiable doit réessayer les livraisons échouées. Mais des tentatives naïves peuvent entraîner un traitement en double et surcharger votre système.
Stratégies de nouvelle tentative
Les modèles de nouvelle tentative courants incluent :
- Backoff exponentiel : Attendez plus longtemps entre chaque tentative (par exemple, 1s, 2s, 4s, 8s...).
- Limite maximale de tentatives : Arrêtez après N tentatives (par exemple, 5) et marquez la livraison comme échouée.
- Jitter : Ajoutez un caractère aléatoire pour éviter les problèmes de troupeau tonnant.
La plupart des fournisseurs implémentent quelque chose comme : réessayer jusqu'à 5 fois avec un backoff exponentiel sur 24 heures.
Idempotence : la clé de tentatives sécurisées
Étant donné que les tentatives peuvent livrer le même événement plusieurs fois, votre point de terminaison doit être idempotent. Incluez un ID d'événement unique dans la charge utile et stockez les ID traités pour ignorer les doublons.
Exemple utilisant un simple ensemble en mémoire (utilisez une base de données en production) :
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
// ... signature verification ...
const event = JSON.parse(payload);
if (processedEvents.has(event.id)) {
return res.status(200).send('Already processed');
}
// Process event
processedEvents.add(event.id);
res.status(200).send('OK');
});
Pour la production, utilisez une base de données avec une contrainte d'unicité sur l'ID d'événement.
Bonnes pratiques pour les consommateurs de webhooks
- Répondez rapidement : Accusez réception avec un statut 2xx avant d'effectuer un traitement lourd. Mettez l'événement en file d'attente pour un traitement asynchrone.
- Validez l'horodatage : Si le fournisseur inclut un horodatage, rejetez les requêtes datant de plus de quelques minutes pour éviter les attaques par rejeu.
- Utilisez HTTPS : Exposez toujours votre point de terminaison webhook via HTTPS pour protéger les charges utiles en transit.
- Journalisez tout : Enregistrez les requêtes entrantes, les signatures et les résultats de traitement pour le débogage.
- Surveillez les échecs : Configurez des alertes pour les échecs de livraison répétés.
Comparaison des politiques de nouvelle tentative
| Politique | Avantages | Inconvénients |
|---|---|---|
| Intervalle fixe | Simple à implémenter | Peut surcharger un système en cours de rétablissement |
| Backoff exponentiel | Réduit la charge pendant les pannes | Retarde la livraison en cas d'échec transitoire |
| Backoff exponentiel avec jitter | Empêche les tentatives synchronisées | Légèrement plus complexe |
FAQ
Comment vérifier une signature de webhook ?
Calculez le HMAC du corps brut de la requête à l'aide du secret partagé et comparez-le à l'en-tête de signature à l'aide d'une fonction à temps constant. Ne comparez jamais avec une simple égalité.
Que faire si mon point de terminaison webhook est indisponible ?
Le fournisseur doit réessayer avec un backoff exponentiel. Assurez-vous que votre point de terminaison renvoie rapidement un 2xx et traite les événements de manière asynchrone pour minimiser l'impact des temps d'arrêt.
Puis-je utiliser des webhooks sans signature ?
Ce n'est pas recommandé. Sans signatures, vous ne pouvez pas vérifier l'expéditeur, ce qui rend votre point de terminaison vulnérable aux faux événements. Utilisez toujours des signatures lorsqu'elles sont disponibles.
Lors du test des charges utiles de webhook, vous devrez peut-être inspecter les données JSON. Utilisez le JSON Formatter pour formater et valider rapidement les charges utiles.