Webhooks expliqués : signatures et tentatives de relance

Backend2026-11-04TryQuickToolBox

Vous venez d'intégrer un fournisseur de paiement tiers qui promet de notifier votre application lorsqu'un paiement est réussi via un webhook. Mais comment savoir si la requête provient réellement de ce fournisseur et non d'un attaquant ? Et que se passe-t-il si votre serveur est brièvement indisponible au moment où le webhook est déclenché ? Sans protections adéquates, vous risquez de traiter de faux événements ou de perdre des mises à jour critiques. Cet article explique comment sécuriser les webhooks avec des signatures de payload et mettre en œuvre des mécanismes de relance robustes.

Qu'est-ce qu'un webhook ?

Un webhook est un rappel HTTP défini par l'utilisateur. Lorsqu'un événement se produit dans un système source (par exemple, un paiement est effectué), il envoie une requête HTTP POST vers une URL que vous fournissez. Contrairement au polling, où vous demandez répétitivement des mises à jour, les webhooks vous poussent les données en quasi-temps réel. Ils sont largement utilisés pour les notifications d'événements, tels que les déclencheurs CI/CD, les messages de chat ou les changements de statut de paiement.

Pourquoi sécuriser les webhooks avec des signatures ?

Les points de terminaison de webhook sont accessibles publiquement, donc n'importe qui peut leur envoyer une requête POST. Sans vérification, un attaquant pourrait falsifier des événements, entraînant des actions non autorisées comme marquer une commande comme payée. Signer les payloads avec un secret partagé vous permet de vérifier l'authenticité et l'intégrité.

Comment fonctionnent les payloads signés

L'émetteur et le récepteur partagent une clé secrète. Pour chaque webhook, l'émetteur calcule une signature cryptographique (généralement un HMAC) sur le corps de la requête et l'inclut dans un en-tête. Le récepteur recalcule la signature en utilisant le même secret et les compare. Si elles correspondent, la requête est authentique.

Choisir le bon algorithme

HMAC-SHA256 est la norme de facto. Il est rapide, sécurisé et disponible dans tous les langages majeurs. Évitez MD5 ou SHA1, considérés comme faibles.

Étape par étape : vérifier un payload signé

  1. Extraire la signature de l'en-tête (par exemple, X-Signature).
  2. Obtenir le corps brut de la requête sous forme de chaîne ou de tableau d'octets. Ne le parsez pas d'abord ; toute transformation modifiera la signature.
  3. Calculer le HMAC en utilisant votre secret et le corps brut.
  4. Comparer la signature calculée avec celle reçue en utilisant une comparaison à temps constant pour éviter les attaques temporelles.

Voici un exemple en Node.js avec Express :

const crypto = require('crypto');
const express = require('express');
const app = express();

// Utiliser un parseur de corps brut pour la route webhook
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;

  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(req.body).digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest))) {
    return res.status(401).send('Invalid signature');
  }

  // Traiter l'événement
  const event = JSON.parse(req.body);
  // ... gérer l'événement

  res.status(200).send('OK');
});

Piège courant : Utiliser un parseur de corps JSON avant la vérification. Le express.json() d'Express parse le corps, donc les octets bruts sont perdus. Utilisez toujours express.raw() pour les routes webhook ou capturez le corps brut séparément.

Concevoir une stratégie de relance

Même avec des signatures, les webhooks peuvent échouer : votre serveur peut être indisponible ou la requête peut expirer. Un émetteur fiable relance les livraisons échouées. En tant que récepteur, vous devez également gérer les relances avec élégance.

Relances côté émetteur

Si vous construisez l'émetteur, implémentez un backoff exponentiel avec jitter. Par exemple, réessayez après 1 minute, puis 5 minutes, puis 30 minutes, jusqu'à un nombre maximum de tentatives (par exemple, 5). Incluez un ID d'événement unique dans le payload pour que le récepteur puisse dédupliquer.

Idempotence côté récepteur

Parce que les relances peuvent provoquer des livraisons en double, votre point de terminaison doit être idempotent. Utilisez l'ID d'événement pour suivre les événements traités. Stockez-le dans une base de données avec une contrainte d'unicité, ou utilisez un cache comme Redis avec un TTL. Si l'ID d'événement a déjà été vu, retournez immédiatement un 200 OK sans retraiter.

Exemple utilisant un index unique de base de données :

async function handleWebhook(event) {
  try {
    await db.query('INSERT INTO processed_events (event_id) VALUES ($1)', [event.id]);
  } catch (err) {
    if (err.code === '23505') { // violation d'unicité
      return; // déjà traité
    }
    throw err;
  }
  // traiter l'événement
}

Meilleures pratiques pour les points de terminaison de webhook

Comparaison : webhooks signés vs non signés

Aspect Webhooks signés Webhooks non signés
Sécurité Élevée – vérifie l'émetteur et l'intégrité Faible – n'importe qui peut falsifier des requêtes
Complexité de mise en œuvre Modérée – nécessite HMAC et gestion des secrets Faible – il suffit d'accepter les POST
Cas d'utilisation Paiements, données sensibles Notifications non critiques

FAQ

Comment vérifier une signature de webhook en Python ?

Utilisez le module hmac avec SHA256. Comparez le digest calculé à l'en-tête en utilisant hmac.compare_digest pour une comparaison à temps constant.

Que faire si mon point de terminaison de webhook reçoit un événement en double ?

Vérifiez l'ID d'événement par rapport à un stockage des ID traités. S'il s'agit d'un doublon, retournez un 200 OK et ignorez le traitement. Cela garantit l'idempotence.

Combien de tentatives de relance un émetteur de webhook doit-il effectuer ?

Il n'y a pas de nombre fixe, mais 3 à 5 relances avec un backoff exponentiel (par exemple, 1 min, 5 min, 30 min) est courant. Après cela, marquez la livraison comme échouée et alertez.

Prêt à tester votre point de terminaison de webhook ? Utilisez notre JSON Formatter pour embellir et valider les payloads pendant le développement.