Aetherio Logo

Webhooks : guide complet pour les intégrer dans vos applications web

14 minutes min de lecture

Partager l'article

Introduction

Le webhook est au développement web ce que la notification push est au smartphone : au lieu de vérifier constamment si quelque chose s'est passé, vous êtes prévenu instantanément. En 2026, 62 % des entreprises utilisent des webhooks pour automatiser des workflows critiques (Zapier, 2024). Paiements Stripe, déploiements GitHub, synchronisation CRM — les webhooks sont partout.

Ce guide couvre l'intégralité du sujet : fonctionnement, implémentation Node.js/Express, sécurité (signatures HMAC), gestion des retries, idempotence et debugging. Avec des exemples de code prêts pour la production. Si vous cherchez un comparatif avec les approches traditionnelles, consultez notre article sur les API REST.

Webhooks guide pratique développement web

Comprendre les Webhooks : Communication Événementielle

Un webhook est un mécanisme de communication asynchrone : une application (l'émetteur) "pousse" des données vers une autre (le récepteur) dès qu'un événement se produit, via une requête HTTP POST.

Webhook vs API Polling

CritèreAPI PollingWebhook
Qui initie ?Le client (requêtes répétées)Le serveur (notification push)
LatenceDépend de l'intervalle de pollingQuasi-instantané
Charge réseauÉlevée (requêtes inutiles à 95 %)Minimale (uniquement si événement)
Complexité clientFaible (boucle de requêtes)Moyenne (endpoint à exposer)
FiabilitéPrévisibleNécessite gestion des retries
Idéal pourDonnées qui changent rarementÉvénements en temps réel

Tandis que les API REST ou GraphQL nécessitent une requête initiée par le client, les webhooks inversent ce flux — rendant les systèmes plus réactifs et économes en ressources.

Comment Fonctionne un Webhook ?

1. Enregistrement    Vous configurez une URL sur votre serveur
                     → https://votreapp.com/webhooks/stripe

2. Événement         Un paiement est réussi sur Stripe

3. Notification      Stripe envoie un POST vers votre URL
                     avec un payload JSON décrivant l'événement

4. Traitement        Votre serveur parse le payload et exécute
                     la logique métier (marquer commande payée)

5. Confirmation      Votre serveur répond 200 OK
                     (sinon Stripe retry automatiquement)

Cas d'Usage Concrets

Paiements et Abonnements (Stripe)

Le cas d'usage le plus critique. Quand un client paie via Stripe, votre application doit être notifiée instantanément. L'intégration Stripe repose entièrement sur les webhooks pour :

  • checkout.session.completed → créer la commande en base
  • invoice.payment_succeeded → renouveler l'abonnement
  • customer.subscription.deleted → désactiver l'accès premium
  • charge.refunded → déclencher le remboursement côté comptabilité

CI/CD et Gestion de Code (GitHub)

Les plateformes Git utilisent intensément les webhooks pour déclencher les pipelines CI/CD :

  • push → lancer les tests automatisés
  • pull_request → déclencher une review automatique
  • release → déployer en production

Automatisation (n8n, Zapier)

Les webhooks sont le point d'entrée principal des plateformes d'automatisation. Un webhook entrant dans n8n peut déclencher un workflow complet : enrichissement CRM, notification Slack, génération de facture.

E-commerce (Shopify, WooCommerce)

  • order_created → mettre à jour l'inventaire
  • product_updated → synchroniser le catalogue
  • refund_issued → notifier le service client

Implémenter un Endpoint Webhook en Node.js

Endpoint de Base (Express)

import express from 'express';

const app = express();

// IMPORTANT : pour Stripe, le body doit être parsé en raw AVANT express.json()
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = req.body;
  const sig = req.headers['stripe-signature'] as string;

  let event;
  try {
    event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Traitement selon le type d'événement
  switch (event.type) {
    case 'checkout.session.completed':
      await handleCheckoutCompleted(event.data.object);
      break;
    case 'invoice.payment_succeeded':
      await handlePaymentSucceeded(event.data.object);
      break;
    case 'customer.subscription.deleted':
      await handleSubscriptionCanceled(event.data.object);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  res.status(200).json({ received: true });
});

Endpoint Webhook Générique

Pour recevoir des webhooks de n'importe quel service (pas seulement Stripe) :

app.post('/webhooks/:source', express.json(), async (req, res) => {
  const source = req.params.source;
  const payload = req.body;
  const headers = req.headers;

  // 1. Vérifier la signature (voir section sécurité)
  if (!verifySignature(source, payload, headers)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // 2. Répondre immédiatement (< 5 secondes)
  res.status(200).json({ received: true });

  // 3. Traiter en arrière-plan (via queue)
  await webhookQueue.add('process-webhook', {
    source,
    payload,
    receivedAt: new Date().toISOString()
  });
});

Règle critique : répondez 200 OK avant de traiter le webhook. Si votre traitement prend plus de 5-10 secondes, le service émetteur considérera le webhook comme échoué et retentera. Utilisez une file d'attente Redis/BullMQ pour le traitement asynchrone.

Sécurité : Vérification des Signatures HMAC

Sans vérification de signature, n'importe qui peut envoyer un faux webhook à votre endpoint. La plupart des services signent leurs webhooks avec HMAC-SHA256.

Comment Fonctionne la Signature

1. Le service (Stripe) calcule un hash HMAC du body avec un secret partagé
2. Il envoie ce hash dans un header (ex: Stripe-Signature, X-Hub-Signature-256)
3. Votre serveur recalcule le hash avec le même secret
4. Si les deux hash correspondent, le webhook est authentique

Implémentation HMAC Générique

import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string | Buffer,
  signature: string,
  secret: string,
  algorithm = 'sha256'
): boolean {
  const expected = crypto
    .createHmac(algorithm, secret)
    .update(payload)
    .digest('hex');

  // Comparaison en temps constant pour éviter les timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Middleware de vérification
function requireSignature(secret: string, headerName: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    const signature = req.headers[headerName] as string;
    if (!signature || !verifyWebhookSignature(req.rawBody, signature, secret)) {
      return res.status(401).json({ error: 'Invalid webhook signature' });
    }
    next();
  };
}

La vérification de signature est un aspect critique de la sécurité des applications web — ne la sautez jamais en production.

Idempotence : Traiter un Webhook une Seule Fois

Les services émetteurs retentent les webhooks en cas d'échec (ou même parfois sans raison). Votre endpoint doit être idempotent — traiter le même événement deux fois ne doit pas créer de doublons.

Implémentation avec Deduplication

async function processWebhook(event: WebhookEvent) {
  // Vérifier si cet événement a déjà été traité
  const alreadyProcessed = await db.query(
    'SELECT id FROM webhook_events WHERE event_id = $1',
    [event.id]
  );

  if (alreadyProcessed.rows.length > 0) {
    console.log(`Event ${event.id} already processed, skipping`);
    return;
  }

  // Traiter l'événement dans une transaction
  await db.transaction(async (tx) => {
    // Enregistrer l'événement comme traité
    await tx.query(
      'INSERT INTO webhook_events (event_id, type, processed_at) VALUES ($1, $2, NOW())',
      [event.id, event.type]
    );

    // Exécuter la logique métier
    switch (event.type) {
      case 'checkout.session.completed':
        await createOrder(tx, event.data);
        break;
      // ...
    }
  });
}

La table webhook_events sert de registre de déduplication. Ajoutez un index unique sur event_id et un TTL pour nettoyer les anciennes entrées.

Gestion des Retries et Ordre des Événements

Politique de Retry des Principaux Services

ServiceNombre de retriesIntervalleTimeout
Stripe3 jours, ~15 tentativesBackoff exponentiel20 secondes
GitHub3 tentatives10 secondes entre chaque10 secondes
Shopify48h, 19 tentativesBackoff exponentiel5 secondes

Ordre des Événements

Les webhooks ne garantissent pas l'ordre d'arrivée. Un invoice.paid peut arriver avant le checkout.completed correspondant. Deux stratégies :

1. Traitement tolérant à l'ordre — votre logique vérifie l'état actuel avant d'agir :

async function handlePaymentSucceeded(invoice: StripeInvoice) {
  const order = await db.findOrder(invoice.metadata.orderId);
  if (!order) {
    // Commande pas encore créée — le webhook checkout arrivera après
    // Stocker l'événement pour retraitement
    await webhookQueue.add('retry-payment', invoice, { delay: 30_000 });
    return;
  }
  await db.markOrderPaid(order.id);
}

2. Event sourcing — stocker tous les événements bruts et reconstruire l'état :

// Stocker l'événement brut
await db.query(
  'INSERT INTO events (source, type, payload) VALUES ($1, $2, $3)',
  ['stripe', event.type, JSON.stringify(event.data)]
);

// Recalculer l'état depuis tous les événements
await rebuildOrderState(orderId);

Debugging des Webhooks

Outils de Développement Local

En développement local, votre serveur n'est pas accessible depuis internet. Utilisez un tunnel :

# ngrok — expose localhost sur une URL publique
ngrok http 3000
# → https://abc123.ngrok-free.app

# Configurer cette URL dans Stripe Dashboard
# Stripe CLI — plus simple pour Stripe spécifiquement
stripe listen --forward-to localhost:3000/webhooks/stripe
stripe trigger checkout.session.completed

Logging Structuré pour le Debug

app.post('/webhooks/:source', async (req, res) => {
  const webhookId = crypto.randomUUID();

  logger.info({
    webhookId,
    source: req.params.source,
    eventType: req.body.type,
    eventId: req.body.id,
    headers: {
      'content-type': req.headers['content-type'],
      'user-agent': req.headers['user-agent']
    }
  });

  try {
    await processWebhook(req.body);
    logger.info({ webhookId, status: 'processed' });
    res.status(200).json({ received: true });
  } catch (error) {
    logger.error({ webhookId, error: error.message, stack: error.stack });
    res.status(500).json({ error: 'Processing failed' });
  }
});

Pour le monitoring en production, configurez des alertes sur les webhooks qui échouent de manière répétée — c'est souvent le signe d'un bug silencieux.

FAQ : Webhooks pour le Développement Web

FAQ - Questions fréquentes

Conclusion : Les Webhooks, Brique Essentielle de l'Architecture Moderne

Les webhooks ne sont pas un détail d'implémentation — c'est une brique architecturale fondamentale pour toute application web connectée. Maîtriser leur intégration (sécurité HMAC, idempotence, gestion des retries) est la différence entre un système fiable et un système qui perd des événements en silence.

Les règles à retenir : toujours vérifier les signatures, toujours répondre vite (200 OK avant traitement), toujours dédupliquer les événements, et toujours monitorer les échecs. Avec ces fondamentaux en place, vos webhooks deviennent un atout de fiabilité plutôt qu'une source de bugs.

Si vous concevez une application nécessitant des intégrations webhook robustes, contactez notre équipe. Nous construisons des applications web sur mesure et des SaaS avec des intégrations fiables et sécurisées.