Comment concevoir des webhooks fiables ?

Ashley Innocent

Ashley Innocent

13 March 2026

Comment concevoir des webhooks fiables ?

Apidog pour les entreprises

Déploiement sur site

SSO & RBAC

Conforme SOC 2

Explorer Apidog Enterprise

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.

💡
Si vous créez ou testez des webhooks, Apidog vous aide à tester la livraison des webhooks, à valider les signatures et à simuler des scénarios d'échec. Vous pouvez tester la logique de réessai et vérifier la gestion de l'idempotence.
bouton

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 :

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 :

Ne pas réessayer en cas de :

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

Sécurité

Tester les webhooks avec Apidog

Apidog prend en charge les tests de webhook.

Tester la livraison des webhooks

  1. Créez un point de terminaison de webhook factice dans Apidog
  2. Enregistrez le point de terminaison auprès de PetstoreAPI
  3. Déclenchez un événement (adopter un animal)
  4. Vérifiez la réception du webhook
  5. 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

  1. Renvoie une erreur 500 à partir du point de terminaison factice
  2. Vérifiez les tentatives de réessai avec un retrait exponentiel
  3. Vérifiez la file d'attente des lettres mortes après le nombre maximal de réessais

Tester l'idempotence

  1. Recevoir le webhook
  2. Renvoie 200
  3. Recevez le même webhook à nouveau (réessai simulé)
  4. Vérifiez qu'il n'y a pas de traitement en double

Conclusion

Des webhooks fiables nécessitent :

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.

bouton

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.

Pratiquez le Design-first d'API dans Apidog

Découvrez une manière plus simple de créer et utiliser des API