Webhooks expliqués : charges utiles signées et logique de retry

Backend2026-10-15TryQuickToolBox

Vous venez d'intégrer une passerelle de paiement. La documentation indique : « Nous enverrons un webhook lorsqu'un paiement réussit. » Parfait. Mais ensuite, vous vous demandez : comment savoir que la requête vient vraiment d'eux ? Et si mon serveur est indisponible au moment de l'envoi ? Et s'ils envoient le même événement deux fois ?

Les webhooks sont puissants mais comportent des pièges. Ce guide détaille les mécanismes, la sécurité et les patterns de fiabilité nécessaires pour les gérer correctement.

Qu'est-ce qu'un webhook ?

Un webhook est une requête HTTP POST qu'un système envoie à un autre lorsqu'un événement spécifique se produit. Au lieu que votre application interroge une API toutes les quelques secondes (« De nouvelles commandes ? De nouvelles commandes ? »), le fournisseur pousse les données vers vous dès que quelque chose se produit.

Flux typique :

  1. Vous enregistrez une URL (par ex. https://yourapp.com/webhooks/payments) auprès du fournisseur.
  2. Un événement se produit (paiement réussi, inscription d'un utilisateur, fichier téléversé).
  3. Le fournisseur envoie une requête POST à votre URL avec un corps JSON décrivant l'événement.
  4. Votre serveur traite l'événement et répond avec 2xx pour accuser réception.

Si vous ne répondez pas rapidement (généralement dans les 5 à 30 secondes), le fournisseur considère la livraison comme échouée et peut réessayer.

Pourquoi vous devez vérifier les charges utiles signées

Votre endpoint webhook est accessible publiquement. Quiconque découvre l'URL peut envoyer de faux événements. Sans vérification, un attaquant pourrait forger un événement « paiement réussi » et obtenir des biens gratuitement.

La solution : les charges utiles signées. Le fournisseur calcule une signature cryptographique sur le corps de la requête à l'aide d'un secret partagé (connu uniquement de vous et de lui) et l'inclut dans un en-tête. Vous recalculez la signature de votre côté et comparez.

Comment fonctionne la signature HMAC

La plupart des fournisseurs utilisent HMAC-SHA256. Les étapes :

  1. Le fournisseur concatène le corps brut de la requête avec un horodatage (pour prévenir les attaques par rejeu).
  2. Le fournisseur calcule HMAC-SHA256(secret, timestamp + '.' + body).
  3. Le fournisseur envoie la signature dans un en-tête, par ex. X-Signature: t=1234567890,v1=abc123....
  4. Vous extrayez l'horodatage et la signature, recalculez le HMAC avec votre copie du secret et comparez à l'aide d'une fonction de comparaison à temps constant.

Voici un exemple minimal en Node.js :

const crypto = require('crypto');

function verifySignature(req, secret) {
  const signatureHeader = req.headers['x-signature'];
  const [tPart, vPart] = signatureHeader.split(',');
  const timestamp = tPart.split('=')[1];
  const receivedSig = vPart.split('=')[1];

  const payload = timestamp + '.' + JSON.stringify(req.body);
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  // Comparaison à temps constant pour éviter les attaques temporelles
  return crypto.timingSafeEqual(
    Buffer.from(receivedSig, 'hex'),
    Buffer.from(expectedSig, 'hex')
  );
}

Important : utilisez toujours le corps brut de la requête pour la vérification de signature. Si votre framework parse le JSON puis le re-sérialise, la séquence d'octets peut différer et faire échouer la vérification. Capturez le corps brut avant le parsing.

Validation de l'horodatage

Vérifiez que l'horodatage est récent (par ex. dans les 5 minutes). Cela empêche les attaques par rejeu où un attaquant capture une requête valide et la renvoie plus tard.

Logique de retry : gérer les échecs avec élégance

Votre serveur peut être indisponible, surchargé ou subir un problème réseau temporaire. Les fournisseurs gèrent cela en réessayant les livraisons échouées. Mais vous devez concevoir votre endpoint pour gérer les retries en toute sécurité.

Backoff exponentiel avec jitter

Les fournisseurs réessaient généralement avec des délais croissants : 1 minute, 5 minutes, 30 minutes, 2 heures, etc. C'est ce qu'on appelle le backoff exponentiel. Certains ajoutent un jitter aléatoire pour éviter les problèmes de type « thundering herd ».

De votre côté, vous ne contrôlez pas le calendrier de retry du fournisseur, mais vous devez :

Idempotence : la clé des retries sûrs

Les retries signifient que vous pouvez recevoir le même événement plusieurs fois. Votre traitement doit être idempotent — traiter le même événement deux fois doit avoir le même effet que le traiter une fois.

Approche courante : inclure un ID d'événement unique dans la charge utile. Avant de traiter, vérifiez si vous avez déjà géré cet ID. Si oui, retournez 200 et ignorez.

async function handleWebhook(event) {
  const existing = await db.events.findOne({ id: event.id });
  if (existing) {
    return { status: 200, body: 'Already processed' };
  }

  await db.events.insert({ id: event.id, processedAt: new Date() });
  await processEvent(event);
  return { status: 200, body: 'OK' };
}

Ce pattern garantit que même si le fournisseur envoie l'événement trois fois, vous n'agissez qu'une seule fois.

Comparaison : polling vs webhooks

Aspect Polling Webhooks
Latence Dépend de l'intervalle (par ex. 30 s) Quasi temps réel
Charge serveur Constante, même sans événement Uniquement lors d'événements
Complexité Simple à implémenter Nécessite endpoint, vérification, retries
Fiabilité Vous contrôlez les retries Dépend de la politique de retry du fournisseur

Les webhooks l'emportent pour les mises à jour en temps réel, mais le polling peut servir de solution de repli pour les événements critiques si vous craignez des livraisons manquées.

Checklist des bonnes pratiques

Déboguer les problèmes de webhook

Lorsque les webhooks échouent, la première étape consiste à inspecter la requête brute. Problèmes courants :

Si vous déboguez du trafic HTTP, des outils comme Nginx Log Analyzer peuvent vous aider à repérer des patterns dans les échecs de requêtes et les codes de réponse.

FAQ

Qu'est-ce qu'un secret de webhook et comment le stocker ?

Le secret est une clé partagée utilisée pour signer les charges utiles. Stockez-le dans des variables d'environnement ou un gestionnaire de secrets — jamais dans le code ou le contrôle de version. Faites-le tourner périodiquement si le fournisseur le permet.

Combien de fois un fournisseur va-t-il réessayer un webhook échoué ?

Cela varie selon le fournisseur. Certains réessaient pendant 24 heures avec un backoff exponentiel, d'autres pendant 3 jours. Consultez la documentation de votre fournisseur pour le calendrier exact et le nombre maximal de tentatives.

Puis-je utiliser des webhooks avec des fonctions serverless ?

Oui, mais attention aux limites de temps d'exécution. Accusez réception du webhook rapidement (retournez 200) et déchargez le traitement lourd vers une file d'attente ou une fonction en arrière-plan pour éviter les timeouts.

Prêt à construire des intégrations webhook robustes ? Commencez par vérifier les signatures et implémenter l'idempotence — ces deux étapes vous feront gagner des heures de débogage et préviendront des incidents de sécurité coûteux.