Skip to main content

Webhooks (Webhooks asynchrones)

POUR AGENT IAChatGPTClaude

Modèle architectural d'interaction inter-services asynchrone (Event-driven Push) dans lequel le fournisseur d'événements envoie une requête HTTP POST contenant les données vers l'URL enregistrée du consommateur lors de la survenue d'un événement système.

1. Vue d'ensemble du concept et problématique système

L'intégration traditionnelle de systèmes via le polling constant (Polling / Short Polling) souffre de deux problèmes mutuellement exclusifs :

  1. Latence élevée : Si le client vérifie les changements toutes les 60 secondes, les données sont livrées avec un retard moyen de 30 secondes.
  2. Gaspillage colossal des ressources de calcul : 99 % des requêtes HTTP renvoient une réponse vide 304 Not Modified ou [], surchargeant la base de données et la pile réseau.

Les Webhooks (rappels inversés via HTTP) changent la paradigme de Pull à Push. Au lieu de demander : "Y a-t-il de nouvelles données ?", le consommateur enregistre son URL (Webhook Listener). Dès qu'un événement se produit dans le système du fournisseur (transaction Stripe réussie, nouveau commit GitHub, message entrant Telegram), le fournisseur envoie une requête HTTP POST avec un JSON payload directement sur le serveur de l'abonné.

Polling traditionnel (Gaspillage de ressources) :
Client  ---> GET /events?since=... ---> Serveur (Vide : [])
Client  ---> GET /events?since=... ---> Serveur (Vide : [])
Client  ---> GET /events?since=... ---> Serveur (Nouvel événement !)

Webhook piloté par les événements (Push instantané) :
Fournisseur d'événements (Stripe / GitHub / Telegram)
       |
       |  Événement : charge.succeeded (Charge utile de l'événement + signature HMAC)
       |  HTTP POST https://api.mysite.com/webhooks/stripe
       v
Consommateur (Validation de la signature -> 200 OK -> File d'attente en arrière-plan RabbitMQ/Redis)

2. Taxonomie architecturale et modèle mental

Le cycle de vie d'un webhook repose sur trois piliers architecturaux :

  1. Authentification cryptographique (HMAC Signature) :
    • Le fournisseur génère un hash cryptographique du corps de la requête (raw body) à l'aide d'une clé secrète partagée (Webhook Secret), qu'il ajoute dans l'en-tête (par exemple, Stripe-Signature ou X-Hub-Signature-256).
    • Le consommateur calcule le même hash et le valide, éliminant ainsi le risque d'usurpation de requête par un attaquant.
  2. Idempotence et déduplication (Idempotency Engine) :
    • Chaque événement possède un ID global unique (evt_xxxxxxxxxx).
    • Le consommateur vérifie atomiquement la présence de l'ID dans le cache/BD (par exemple, via INSERT ... ON CONFLICT DO NOTHING ou Redis SET key NX EX 86400).
  3. Politique de livraison garantie et de retries (Retry & Dead-Letter Queue) :
    • En cas d'indisponibilité du serveur consommateur, le fournisseur répète les requêtes selon une échelle exponentielle (Exponential Backoff avec jitter) : après 5 min, 30 min, 2 h, 12 h.
    • Les événements non traités après le nombre maximal de tentatives sont dirigés vers une Dead-Letter Queue (DLQ) pour une analyse manuelle par les ingénieurs.

3. Pipeline technique et mécanique interne

Gestionnaire de production avec vérification HMAC (Node.js / Express / TypeScript)

[!IMPORTANT] Pour vérifier la signature, il est impératif de récupérer le corps brut de la requête (raw buffer) et non l'objet parsé req.body. Sinon, le hash ne correspondra pas en raison des divergences d'espaces et de tri des clés JSON.

import express, { Request, Response } from "express";
import crypto from "crypto";

const app = express();

// Отримання raw buffer для ендпоінтів вебхуків
app.post(
  "/api/v1/stripe-webhook",
  express.raw({ type: "application/json" }),
  async (req: Request, res: Response) => {
    const signature = req.headers["stripe-signature"] as string;
    const secret = process.env.STRIPE_WEBHOOK_SECRET!;

    if (!signature) {
      return res.status(400).send("Missing signature header");
    }

    // 1. Криптографічна валідація підпису через HMAC SHA-256
    const computedHmac = crypto
      .createHmac("sha256", secret)
      .update(req.body)
      .digest("hex");

    // Безпечне порівняння за фіксований час
    const isValid = crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(computedHmac)
    );

    if (!isValid) {
      return res.status(401).send("Invalid signature");
    }

    const payload = JSON.parse(req.body.toString());
    const eventId = payload.id;

    // 2. Ідемпотентна перевірка дедуплікації
    const isNew = await redis.set(`webhook:processed:${eventId}`, "1", "NX", "EX", 86400);
    if (!isNew) {
      // Подію вже оброблено — повертаємо 200 OK без повторного виконання
      return res.status(200).json({ status: "already_processed" });
    }

    // 3. Відправка важкої роботи в чергу BullMQ
    await eventQueue.add("process_payment", payload);

    // 4. Миттєве підтвердження отримання
    return res.status(200).json({ received: true });
  }
);

4. Scénarios d'ingénierie pratiques en production

01. Passerelle de paiement pour AI SaaS (Stripe / Paddle / LiqPay)

Un utilisateur paie un abonnement au plan Pro. Le système de paiement génère l'événement invoice.payment_succeeded. Le webhook met instantanément à jour le statut de l'utilisateur dans la base de données, réinitialise les limites de tokens dans Redis et envoie un email de bienvenue, permettant à l'utilisateur de continuer sans recharger la page.

02. Déclencheur de pipeline CI/CD autonome (GitHub Webhooks)

Un ingénieur effectue un git push sur la branche main. GitHub envoie un payload contenant le hash du commit vers votre propre serveur Coolify ou ArgoCD. Le serveur vérifie le secret, clone le code modifié, lance les tests unitaires dans un conteneur Docker isolé et met à jour le service de production selon la stratégie Blue/Green.

03. Collecte asynchrone des résultats d'inférence de modèles IA lourds

Lors de l'appel aux modèles de génération vidéo (Runway, Kling) ou d'entraînement LoRA, la requête dure plusieurs minutes. Au lieu de maintenir une connexion HTTP ouverte, le client transmet une webhook_url. Dès que le cluster GPU termine la génération, il envoie un webhook avec le lien S3 final vers le média généré.


5. Pièges courants, erreurs typiques et sécurité

  1. Parsing JSON avant la vérification de la signature : Si le middleware standard express.json() s'exécute avant la vérification de la signature, le sérialiseur peut modifier l'ordre des champs, l'échappement des caractères Unicode ou le formatage des nombres, entraînant une divergence de la somme de contrôle HMAC et un rejet Invalid Signature.
  2. Blocage synchrone de l'exécution (Webhook Deadlock) : L'exécution de transactions lourdes ou de requêtes HTTP longues vers des API externes à l'intérieur du gestionnaire de webhook entraînera inévitablement un timeout côté fournisseur. Renvoyez 200 OK immédiatement après la validation de la signature et la mise en file d'attente.
  3. Absence de validation des horodatages (Replay Attacks) : Un attaquant ayant intercepté une requête webhook légitime signée peut la renvoyer une heure plus tard. Les fournisseurs incluent un timestamp dans la signature. Vérifiez que la différence entre l'heure de la requête et l'horloge système du serveur ne dépasse pas la fenêtre tolérée (par exemple, 5 minutes).
/ Questions fréquentesSchema.org FAQPage

FAQ: Webhooks (Webhooks asynchrones)

La comparaison classique de chaînes via les opérateurs == ou === s'interrompt au premier caractère non correspondant. Un attaquant peut mesurer la latence de réponse du serveur avec une précision nanoseconde (Timing Attack) et deviner octet par octet un hachage de signature valide. La fonction timingSafeEqual effectue la comparaison en temps constant, garantissant ainsi la robustesse cryptographique.
/ Liens internes
Tous les termes