Webhooks expliqués : signatures et tentatives

Backend2026-10-29TryQuickToolBox

Vous venez d'intégrer un fournisseur de paiement tiers. La documentation indique : « Nous enverrons un webhook lorsqu'un paiement réussit. » Simple, non ? Mais vous réalisez ensuite : comment savoir que la requête provient réellement de chez eux ? Et si votre serveur est hors ligne au moment de l'envoi ? Et s'ils envoient le même événement deux fois ?

Les webhooks sont puissants mais nécessitent une gestion minutieuse. Ce guide couvre l'essentiel : signer les payloads pour vérifier l'authenticité, implémenter des tentatives pour gérer les échecs et assurer l'idempotence pour éviter les traitements en double.

Que sont les webhooks ?

Un webhook est un callback HTTP : au lieu que votre application interroge une API pour détecter des changements, le fournisseur envoie un POST HTTP à une URL que vous spécifiez lorsqu'un événement se produit. Cela rend les intégrations efficaces et quasi temps réel.

Les exemples courants incluent les notifications de paiement (Stripe, PayPal), les déclencheurs CI/CD (GitHub, GitLab) et les plateformes de messagerie (Slack, Discord).

Pourquoi sécuriser les webhooks avec des signatures ?

Votre endpoint de webhook est accessible publiquement. N'importe qui peut lui envoyer une requête POST. Sans vérification, un attaquant pourrait falsifier des événements—par exemple, marquer une commande impayée comme payée.

Signer les payloads avec un secret partagé vous permet de vérifier que la requête provient bien de l'expéditeur attendu et que le payload n'a pas été altéré.

Comment fonctionnent les signatures HMAC

Le fournisseur et vous partagez une clé secrète. Pour chaque webhook, le fournisseur calcule un HMAC (Hash-based Message Authentication Code) du payload à l'aide de ce secret et inclut la signature dans un en-tête (par exemple, X-Signature). Vous recalculez le HMAC de votre côté et comparez.

Le HMAC garantit l'intégrité et l'authenticité car seul quelqu'un possédant le secret peut produire une signature valide.

Implémentation des payloads signés

Parcourons une implémentation typique en Node.js (Express) et Python (Flask). Les principes s'appliquent à tout langage.

Étape 1 : Obtenir et stocker le secret

Lorsque vous enregistrez le webhook, le fournisseur vous donne un secret. Stockez-le de manière sécurisée—variables d'environnement ou gestionnaire de secrets—jamais dans le code.

Étape 2 : Capturer le corps brut

La vérification de la signature nécessite les octets exacts du corps brut. Si votre framework analyse le JSON avant que vous ne vérifiiez, la re-sérialisation peut modifier les octets et casser la vérification.

Dans Express, utilisez express.raw({ type: 'application/json' }) pour la route du webhook. Dans Flask, accédez à request.get_data() avant l'analyse.

Étape 3 : Calculer et comparer les signatures

Utilisez une fonction de comparaison à temps constant pour éviter les attaques temporelles.

// Node.js (Express)
const crypto = require('crypto');

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;
  const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
  
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process event
  const event = JSON.parse(req.body);
  // ... handle event
  res.status(200).send('OK');
});

En Python, utilisez hmac.compare_digest :

# Python (Flask)
import hmac, hashlib, os
from flask import request, abort

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Signature')
    secret = os.environ['WEBHOOK_SECRET'].encode()
    expected = hmac.new(secret, request.get_data(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected):
        abort(401)
    event = request.get_json()
    # ... handle event
    return 'OK', 200

Étape 4 : Gérer les horodatages (optionnel mais recommandé)

Certains fournisseurs incluent un horodatage dans la signature pour prévenir les attaques par rejeu. Par exemple, le schéma de Stripe signe timestamp.payload. Vous devez rejeter les requêtes avec des horodatages trop anciens (par exemple, plus de 5 minutes).

Implémentation des tentatives

Les réseaux échouent. Votre serveur peut être hors ligne ou la requête peut expirer. Les systèmes de webhooks fiables réessaient les livraisons échouées.

Du côté du fournisseur

Si vous construisez l'expéditeur de webhooks, implémentez des tentatives avec un backoff exponentiel. Un modèle courant : réessayer après 1 minute, puis 5 minutes, puis 30 minutes, etc., jusqu'à un nombre maximal de tentatives (par exemple, 5). Incluez un ID d'événement unique dans le payload pour que le récepteur puisse dédupliquer.

Du côté du récepteur

En tant que récepteur, vous devez :

Idempotence : gérer les événements en double

Les tentatives peuvent entraîner des événements en double. Utilisez l'ID d'événement (inclus par la plupart des fournisseurs) pour suivre les événements traités. Avant de traiter, vérifiez si l'ID a déjà été vu ; si oui, ignorez.

Stockez les IDs traités dans une base de données avec une TTL, ou utilisez une clé d'idempotence dans votre logique métier.

Tester les webhooks localement

Pendant le développement, votre serveur local n'est pas accessible publiquement. Des outils comme ngrok ou localtunnel créent une URL publique qui tunnelise vers votre machine. Alternativement, utilisez un service de test de webhooks qui capture les requêtes et vous permet de les rejouer.

Pièges courants et bonnes pratiques

Comparaison : webhooks signés vs non signés

AspectSignéNon signé
SécuritéÉlevée : vérifie l'origine et l'intégritéFaible : n'importe qui peut falsifier
Effort d'implémentationModéré : calculer et comparer le HMACMinimal
Recommandé pourTous les systèmes en productionRéseaux internes de confiance uniquement

FAQ

Comment vérifier une signature de webhook ?

Calculez un HMAC du corps brut de la requête en utilisant le secret partagé, puis comparez-le à l'en-tête de signature avec une fonction à temps constant. Utilisez toujours le corps brut, pas le JSON analysé.

Que doit retourner mon endpoint de webhook ?

Retournez rapidement un code de statut 2xx pour accuser réception. Si le traitement échoue, retournez un 5xx pour déclencher une nouvelle tentative, mais assurez-vous que votre endpoint est idempotent pour gérer les doublons.

Combien de fois un webhook sera-t-il réessayé ?

Cela dépend du fournisseur. La plupart réessaient plusieurs fois avec un backoff exponentiel sur une période de quelques heures ou jours. Consultez la documentation du fournisseur pour plus de détails.

Lors du débogage des livraisons de webhooks, l'inspection des journaux du serveur est souvent nécessaire. Si vous utilisez Nginx, l'Analyseur de journaux Nginx peut vous aider à analyser les journaux d'accès pour repérer les requêtes échouées ou les schémas inhabituels.