Webhooks expliqués : signatures et tentatives

Backend2026-10-22TryQuickToolBox

Vous avez créé un point de terminaison webhook pour recevoir des événements d'un service tiers. Tout fonctionne en test, mais en production, vous remarquez des événements en double, des notifications manquées et parfois des problèmes de sécurité liés à des requêtes falsifiées. Ce sont des pièges courants lorsqu'on implémente des webhooks sans les protections adéquates.

Les webhooks sont des callbacks HTTP qui permettent aux services de notifier votre application lorsqu'un événement se produit. Contrairement au polling, ils fournissent des mises à jour en temps réel. Mais pour les utiliser de manière fiable, vous devez aborder deux aspects critiques : l'authenticité (s'assurer que la requête provient de l'expéditeur attendu) et la livraison (gérer les échecs et les tentatives). Cet article explique les deux avec des exemples de code pratiques.

Comment fonctionnent les webhooks

Un webhook est un point de terminaison HTTP POST défini par l'utilisateur. Lorsqu'un événement se produit, le service source envoie une requête à cette URL avec un payload décrivant l'événement. Par exemple, un processeur de paiement peut envoyer un webhook lorsqu'un paiement réussit.

Le flux est simple :

  1. Vous enregistrez une URL de webhook auprès du service.
  2. Le service envoie un HTTP POST à cette URL lorsqu'un événement se déclenche.
  3. Votre point de terminaison traite le payload et renvoie un statut 2xx pour accuser réception.

Sans vérification appropriée, n'importe qui peut envoyer des requêtes à votre point de terminaison. Sans gestion des tentatives, des échecs transitoires peuvent entraîner des événements perdus. Abordons chacun de ces points.

Sécuriser les webhooks avec des payloads signés

Pour garantir qu'un webhook provient de l'expéditeur attendu, les services signent le payload à l'aide d'un secret partagé. La signature est généralement un HMAC (Hash-based Message Authentication Code) du corps de la requête, envoyé dans un en-tête comme X-Signature.

Comment fonctionne la signature HMAC

L'expéditeur calcule un HMAC à l'aide d'une clé secrète et du corps brut de la requête, puis inclut la signature encodée en hexadécimal dans un en-tête. Votre point de terminaison recalcule le HMAC avec le même secret et le compare à la signature reçue. S'ils correspondent, la requête est authentique.

Voici un exemple minimal en Node.js avec Express :

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

// Utiliser le corps brut pour la vérification de la signature
app.use(express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;
  const payload = req.body;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

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

  // Traiter l'événement
  const event = JSON.parse(payload);
  // ... gérer l'événement
  res.status(200).send('OK');
});

Points clés :

Mesures de sécurité supplémentaires

Implémenter des tentatives fiables

La livraison des webhooks n'est pas garantie. Les réseaux échouent, votre serveur peut être indisponible ou vous pouvez renvoyer un statut non-2xx. Pour gérer cela, les expéditeurs réessaient généralement avec un backoff exponentiel. En tant que récepteur, vous devez concevoir votre point de terminaison pour être idempotent et répondre rapidement.

Côté expéditeur (si vous envoyez des webhooks)

Si vous créez un service qui envoie des webhooks, implémentez des tentatives avec backoff. Une stratégie courante :

Exemple de logique de tentative en Python :

import time
import requests
from requests.exceptions import RequestException

def send_webhook(url, payload, max_retries=5):
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=payload, timeout=5)
            if 200 <= response.status_code < 300:
                return True
            elif 400 <= response.status_code < 500 and response.status_code != 429:
                # Erreur client, ne pas réessayer
                return False
        except RequestException:
            pass
        # Backoff exponentiel avec jitter
        delay = (2 ** attempt) + random.uniform(0, 1)
        time.sleep(delay)
    return False

Côté récepteur

Votre point de terminaison doit :

Exemple d'idempotence utilisant une contrainte d'unicité de base de données :

CREATE TABLE processed_events (
  event_id VARCHAR(255) PRIMARY KEY,
  processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Avant de traiter, essayez d'insérer l'ID de l'événement. S'il viole la clé primaire, ignorez le traitement.

Tester les webhooks localement

Pendant le développement, vous avez besoin d'une URL publique pour que le service puisse atteindre votre serveur local. Des outils comme ngrok ou localtunnel exposent votre localhost. Utilisez toujours des secrets de test et vérifiez les signatures en développement pour détecter les problèmes tôt.

Comparaison : polling vs webhooks

Aspect Polling Webhooks
Latence Dépend de l'intervalle Quasi temps réel
Charge serveur Élevée (requêtes fréquentes) Faible (uniquement sur événements)
Complexité Simple à implémenter Nécessite la vérification de signature et la gestion des tentatives
Fiabilité Vous contrôlez les tentatives Dépend de la politique de tentative de l'expéditeur

FAQ

Qu'est-ce qu'un payload webhook signé ?

Un payload signé inclut une signature cryptographique (généralement HMAC) générée avec un secret partagé. Elle permet au récepteur de vérifier que la requête provient de l'expéditeur attendu et n'a pas été altérée.

Comment gérer les livraisons de webhooks en double ?

Rendez votre point de terminaison idempotent en utilisant un ID d'événement unique provenant du payload. Stockez les ID traités et ignorez les doublons. Cela garantit que les tentatives ou les problèmes réseau ne provoquent pas de double traitement.

Dois-je réessayer les livraisons de webhooks échouées ?

Oui, mais uniquement pour les erreurs transitoires (problèmes réseau, 5xx). Utilisez un backoff exponentiel avec jitter et fixez une limite maximale de tentatives. Évitez de réessayer en cas d'erreurs 4xx (sauf 429), car elles indiquent un problème avec la requête elle-même.

Lors du débogage de payloads webhook, vous avez souvent besoin d'inspecter des données JSON. Le JSON Formatter peut vous aider à formater et valider rapidement les payloads JSON.