En bref
Concevez des webhooks fiables avec une logique de réessai à retrait exponentiel (5 à 10 tentatives), des clés d'idempotence, la vérification de signature HMAC et des délais d'attente de 5 secondes. Retournez un code 2xx immédiatement, traitez de manière asynchrone. L'API PetstoreAPI moderne implémente des webhooks pour les mises à jour de commandes, les adoptions d'animaux et les notifications de paiement avec une logique de réessai et de sécurité complète.
Introduction
Vous envoyez un webhook pour notifier un client que son animal a été adopté. Le serveur du client est en panne. Votre webhook échoue. Faut-il réessayer ? Combien de fois ? Que se passe-t-il si le client reçoit le webhook deux fois et facture le client deux fois ?
Les webhooks sont des rappels HTTP qui envoient des événements aux URL des clients. Ils sont simples en théorie mais complexes en pratique. Les réseaux tombent en panne, les serveurs plantent et les clients ont des bugs. Les webhooks de production nécessitent une logique de réessai, une idempotence, une sécurité et une surveillance.
Modern PetstoreAPI implémente des webhooks prêts pour la production pour les mises à jour de commandes, les adoptions d'animaux et les notifications de paiement. Chaque webhook inclut une logique de réessai, la vérification de signature et l'idempotence.
Dans ce guide, vous apprendrez à concevoir des webhooks fiables en utilisant les modèles de Modern PetstoreAPI.
Principes de base des webhooks
Les webhooks sont des requêtes HTTP POST envoyées aux URL fournies par le client lorsque des événements se produisent.
Comment fonctionnent les webhooks
1. Le client enregistre l'URL du webhook :
POST /webhooks
{
"url": "https://client.com/webhooks/petstore",
"events": ["pet.adopted", "order.completed"]
}
2. Un événement se produit (animal adopté)
3. Le serveur envoie le webhook :
POST https://client.com/webhooks/petstore
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
{
"event": "pet.adopted",
"data": {
"petId": "019b4132",
"userId": "user-456",
"timestamp": "2026-03-13T10:30:00Z"
}
}
4. Le client répond :
200 OK
Le problème de fiabilité
Les webhooks peuvent échouer pour de nombreuses raisons :
- Le serveur du client est en panne
- Délai d'expiration du réseau
- Le client renvoie une erreur 500
- Le client est lent (prend 30 secondes)
- Le client reçoit le webhook mais plante avant le traitement
Sans logique de réessai, les événements sont perdus. Sans idempotence, les webhooks en double provoquent des actions en double.
Logique de réessai avec retrait exponentiel
Réessayez les webhooks échoués avec des délais croissants.
Stratégie de retrait exponentiel
Tentative 1 : Immédiate
Tentative 2 : 1 seconde plus tard
Tentative 3 : 2 secondes plus tard
Tentative 4 : 4 secondes plus tard
Tentative 5 : 8 secondes plus tard
Tentative 6 : 16 secondes plus tard
Pourquoi exponentiel ? Si le client est en panne, le marteler de réessais ne servira à rien. Le retrait exponentiel lui laisse le temps de se rétablir.
Implémentation
async function sendWebhook(url, payload, attempt = 1, maxAttempts = 6) {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': generateSignature(payload)
},
body: JSON.stringify(payload),
timeout: 5000 // 5 second timeout
});
if (response.ok) {
return { success: true, attempt };
}
// Réessayez en cas d'erreurs 5xx
if (response.status >= 500 && attempt < maxAttempts) {
const delay = Math.pow(2, attempt - 1) * 1000;
await sleep(delay);
return sendWebhook(url, payload, attempt + 1, maxAttempts);
}
// Ne réessayez pas les erreurs 4xx (erreur client)
return { success: false, status: response.status };
} catch (error) {
// Erreur réseau ou délai d'attente - réessayez
if (attempt < maxAttempts) {
const delay = Math.pow(2, attempt - 1) * 1000;
await sleep(delay);
return sendWebhook(url, payload, attempt + 1, maxAttempts);
}
return { success: false, error: error.message };
}
}
Quand réessayer
Réessayer en cas de :
- Erreurs de serveur 5xx (500, 502, 503, 504)
- Dépassements de délai réseau
- Connexion refusée
- Échecs DNS
Ne pas réessayer en cas de :
- Erreurs client 4xx (400, 401, 404) - le client ne les corrigera pas
- Succès 2xx - déjà réussi
File d'attente de lettres mortes (Dead Letter Queue)
Après le nombre maximal de réessais, déplacez les webhooks échoués vers une file d'attente de lettres mortes pour un examen manuel :
if (!result.success) {
await deadLetterQueue.add({
url,
payload,
attempts: maxAttempts,
lastError: result.error,
timestamp: new Date()
});
}
Idempotence pour la prévention des doublons
Les clients peuvent recevoir le même webhook plusieurs fois. L'idempotence empêche le traitement en double.
Clés d'idempotence
Incluez un ID unique avec chaque webhook :
{
"id": "webhook_019b4132",
"event": "pet.adopted",
"data": {...}
}
Le client stocke les ID traités :
app.post('/webhooks/petstore', async (req, res) => {
const webhookId = req.body.id;
// Vérifier si déjà traité
const processed = await db.webhooks.findOne({ id: webhookId });
if (processed) {
return res.status(200).json({ message: 'Déjà traité' });
}
// Traiter le webhook
await processPetAdoption(req.body.data);
// Marquer comme traité
await db.webhooks.insert({ id: webhookId, processedAt: new Date() });
res.status(200).json({ message: 'Traité' });
});
Opérations idempotentes
Concevoir des opérations pour qu'elles soient idempotentes :
Mauvais (non idempotent) :
// Facturer deux fois entraîne une double facturation
await chargeCustomer(userId, amount);
Bon (idempotent) :
// Facturer avec une clé d'idempotence empêche la double facturation
await chargeCustomer(userId, amount, { idempotencyKey: webhookId });
Vérification de signature pour la sécurité
Vérifiez que les webhooks proviennent de votre API, et non d'un attaquant.
Signature HMAC
Générez la signature à l'aide d'un secret partagé :
// Le serveur génère la signature
const crypto = require('crypto');
function generateSignature(payload, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload));
return hmac.digest('hex');
}
// Inclure dans l'en-tête
headers['X-Webhook-Signature'] = `sha256=${generateSignature(payload, webhookSecret)}`;
Le client vérifie la signature :
function verifySignature(payload, signature, secret) {
const expected = generateSignature(payload, secret);
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
app.post('/webhooks/petstore', (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Signature invalide' });
}
// Traiter le webhook
...
});
Validation de l'horodatage
Incluez l'horodatage pour empêcher les attaques par relecture :
{
"id": "webhook_019b4132",
"timestamp": "2026-03-13T10:30:00Z",
"event": "pet.adopted",
"data": {...}
}
Rejeter les anciens webhooks :
const webhookAge = Date.now() - new Date(req.body.timestamp);
if (webhookAge > 5 * 60 * 1000) { // 5 minutes
return res.status(400).json({ error: 'Webhook trop ancien' });
}
Gestion des délais d'attente
Définissez des délais d'attente agressifs pour empêcher les clients lents de bloquer votre système.
Délai d'attente de 5 secondes
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(payload),
timeout: 5000 // 5 seconds
});
Pourquoi 5 secondes ? Les webhooks doivent répondre immédiatement. Si un client prend plus de temps, il effectue un traitement synchrone (mauvais modèle).
Modèle de traitement asynchrone
Mauvais (synchrone) :
app.post('/webhooks/petstore', async (req, res) => {
// Cela prend 30 secondes - le webhook expirera
await processOrder(req.body.data);
await sendEmail(req.body.data);
await updateInventory(req.body.data);
res.status(200).json({ message: 'Traité' });
});
Bon (asynchrone) :
app.post('/webhooks/petstore', async (req, res) => {
// Répondre immédiatement
res.status(200).json({ message: 'Reçu' });
// Traiter de manière asynchrone
queue.add('process-webhook', req.body);
});
Comment Modern PetstoreAPI implémente les webhooks
Modern PetstoreAPI implémente des webhooks prêts pour la production.
Événements de webhook
pet.adopted - L'animal a été adopté
pet.status_changed - Le statut de l'animal a changé
order.created - Commande créée
order.completed - Commande terminée
payment.succeeded - Paiement réussi
payment.failed - Paiement échoué
Charge utile du webhook
{
"id": "webhook_019b4132-70aa-764f-b315-e2803d882a24",
"event": "pet.adopted",
"timestamp": "2026-03-13T10:30:00Z",
"data": {
"petId": "019b4132-70aa-764f-b315-e2803d882a24",
"userId": "user-456",
"orderId": "order-789",
"adoptionDate": "2026-03-13"
},
"apiVersion": "v1"
}
Configuration des réessais
- Nombre maximal de tentatives : 10
- Retrait : Exponentiel (1s, 2s, 4s, 8s, 16s, 32s, 64s, 128s, 256s, 512s)
- Fenêtre de réessai totale : environ 17 minutes
- File d'attente de lettres mortes après le nombre maximal de réessais
Sécurité
- Signature HMAC-SHA256 dans l'en-tête
X-Webhook-Signature - Validation de l'horodatage (rejet si > 5 minutes)
- HTTPS requis pour les URL de webhook
Tester les webhooks avec Apidog
Apidog prend en charge les tests de webhook.
Tester la livraison des webhooks
- Créez un point de terminaison de webhook factice dans Apidog
- Enregistrez le point de terminaison auprès de PetstoreAPI
- Déclenchez un événement (adopter un animal)
- Vérifiez la réception du webhook
- Vérifiez le format de la charge utile
Tester la vérification de signature
// Script de test Apidog
const signature = pm.request.headers.get('X-Webhook-Signature');
const payload = pm.request.body.raw;
const secret = pm.environment.get('WEBHOOK_SECRET');
const expected = generateSignature(payload, secret);
pm.test('Signature valide', () => {
pm.expect(signature).to.equal(`sha256=${expected}`);
});
Tester la logique de réessai
- Renvoie une erreur 500 à partir du point de terminaison factice
- Vérifiez les tentatives de réessai avec un retrait exponentiel
- Vérifiez la file d'attente des lettres mortes après le nombre maximal de réessais
Tester l'idempotence
- Recevoir le webhook
- Renvoie 200
- Recevez le même webhook à nouveau (réessai simulé)
- Vérifiez qu'il n'y a pas de traitement en double
Conclusion
Des webhooks fiables nécessitent :
- Une logique de réessai à retrait exponentiel (5-10 tentatives)
- Des clés d'idempotence pour éviter les doublons
- La vérification de signature HMAC pour la sécurité
- Des délais d'attente de 5 secondes
- Un traitement asynchrone côté client
- Une file d'attente de lettres mortes pour les webhooks échoués
Modern PetstoreAPI implémente tous ces modèles. Consultez la documentation des webhooks pour des exemples complets.
Testez vos webhooks avec Apidog pour vérifier la logique de réessai, les signatures et l'idempotence avant de passer en production.
FAQ
Combien de tentatives de réessai les webhooks doivent-ils avoir ?
5 à 10 tentatives avec un retrait exponentiel. Cela couvre les pannes temporaires (5 à 17 minutes) sans surcharger le client.
Les webhooks doivent-ils réessayer en cas d'erreurs 4xx ?
Non. Les erreurs 4xx indiquent des problèmes côté client (mauvaise URL, échec d'authentification). Les réessais ne les corrigeront pas. Ne réessayez que les erreurs 5xx et les échecs réseau.
Quelle doit être la durée des délais d'attente des webhooks ?
5 secondes maximum. Les clients doivent renvoyer 200 immédiatement et traiter de manière asynchrone. Des délais d'attente plus longs indiquent que le client effectue un traitement synchrone.
Que faire si un client ne répond jamais aux webhooks ?
Après le nombre maximal de réessais, déplacez-le vers la file d'attente des lettres mortes. Alertez le client par e-mail. Envisagez de désactiver les webhooks pour ce client après des échecs répétés.
Les URL des webhooks doivent-elles être HTTPS ?
Oui, exigez toujours HTTPS. Les webhooks HTTP peuvent être interceptés et modifiés. Modern PetstoreAPI rejette les URL de webhook HTTP.
Comment prévenir les attaques par relecture ?
Incluez l'horodatage dans la charge utile et rejetez les webhooks de plus de 5 minutes. Combinez cela avec la vérification de signature.
Les clients peuvent-ils demander une nouvelle livraison de webhook ?
Oui. Modern PetstoreAPI fournit un point de terminaison pour redélivrer des webhooks spécifiques : POST /webhooks/{id}/redeliver
Comment tester les webhooks localement ?
Utilisez des outils comme ngrok pour exposer localhost à Internet, ou utilisez le serveur de maquette d'Apidog pour simuler des points de terminaison de webhook pendant le développement.
