Un webhook est un message HTTP qu’une application envoie automatiquement vers une URL que vous avez configurée dès qu’un événement se produit – c’est l’exact inverse d’une API que vous interrogez en boucle en attendant une réponse. Si vous avez déjà branché Stripe, Shopify ou Make, vous avez utilisé des webhooks sans forcément mettre ce mot dessus.
En bref
- Un webhook est une notification push : c’est l’émetteur qui appelle votre serveur, pas l’inverse.
- Il arrive presque toujours en POST, avec un payload JSON dans le corps de la requête.
- La sécurité repose sur deux piliers : la vérification de signature HMAC et la gestion de l’idempotence par identifiant d’événement.
- Les émetteurs sérieux (Stripe, GitHub, Shopify) réessaient en cas d’échec : votre endpoint doit pouvoir recevoir plusieurs fois le même événement sans casser.
- On teste un webhook avec
webhook.site, le Stripe CLI ou un tunnel type ngrok.
Webhook ou API en polling : quelle différence ?
Avant de rentrer dans le technique, posez la vraie question : qui prend l’initiative de la communication ?
| Critère | Webhook | API en polling |
|---|---|---|
| Sens de l’appel | L’émetteur pousse vers vous | Vous tirez depuis votre côté |
| Latence | Immédiate (quelques secondes) | Dépend de l’intervalle (souvent 1 à 15 min) |
| Charge réseau | Faible, un appel par événement | Élevée, beaucoup d’appels vides |
| Complexité | Endpoint à exposer, sécurité à gérer | Plus simple à implémenter |
| Idéal pour | Événements temps réel (paiement, commande) | Synchronisation périodique, récupération d’historique |
Le polling reste utile – notamment pour récupérer des données historiques – mais pour un événement temps réel, le webhook est la bonne réponse. Une boutique qui attend 15 minutes pour livrer un accès après paiement, c’est un client perdu.
Comment fonctionne un webhook (POST, payload, headers)
Le mécanisme est bête, mais il faut le voir en entier :
- Vous enregistrez une URL (« l’endpoint ») dans le service émetteur, par exemple
https://api.mon-site.fr/webhooks/stripe. Configurer l’URL d’un webhook sur votre site revient à donner une adresse postale au service. - Un événement se produit côté émetteur : paiement confirmé, commande créée, formulaire soumis.
- L’émetteur envoie une requête HTTP POST vers cette URL. Le corps contient les données de l’événement : c’est le payload.
- Les en-têtes HTTP (headers) transportent les métadonnées : le type de contenu (
Content-Type: application/json), la signature (Stripe-Signature,X-Hub-Signature-256…), parfois un identifiant de livraison. - Votre serveur répond avec un code
2xxpour confirmer la réception. Un code non-2xx(ou un timeout) est interprété comme un échec, et déclenche un nouvel essai.
Le corps de la requête est la partie la plus importante : Content-Type: application/json signale un payload JSON, que vous devez lire brut avant de le parser (on verra pourquoi au moment de la signature).

Un exemple de payload JSON réel
Voici à quoi ressemble un événement Stripe checkout.session.completed, simplifié mais fidèle à la structure réelle :
POST /webhooks/stripe HTTP/1.1
Host: api.mon-site.fr
Content-Type: application/json
Stripe-Signature: t=1735689600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
"id": "evt_1QxExample",
"object": "event",
"type": "checkout.session.completed",
"created": 1735689600,
"data": {
"object": {
"id": "cs_test_a1b2c3",
"amount_total": 4900,
"currency": "eur",
"payment_status": "paid",
"customer_details": {
"email": "client@exemple.fr"
}
}
}
}
Trois champs comptent plus que les autres :
id: l’identifiant unique de l’événement. C’est votre clé d’idempotence (voir plus bas).type: le nom de l’événement, qui détermine le traitement à appliquer.data.object: la ressource concernée, à utiliser pour agir (ici, activer un abonnement).
Vérifier la signature (HMAC), gérer les retries et l’idempotence
C’est le cœur du sujet, et ce que la plupart des articles « c’est quoi un webhook » sautent.
La signature HMAC
Un webhook arrive par Internet : n’importe qui peut appeler votre endpoint. Pour vérifier que la requête vient bien de l’émetteur et n’a pas été modifiée, on utilise une signature HMAC. L’émetteur calcule un HMAC-SHA-256 sur le corps de la requête avec un secret partagé, et l’envoie dans un header. Vous recalculez le même HMAC de votre côté et vous comparez.
Règle d’or : on calcule la signature sur le corps brut, avant tout parsing ou décodage JSON. Si vous parsez puis re-sérialisez, l’ordre des clés ou les espaces changent et la signature ne correspond plus.
const crypto = require('crypto');
// `rawBody` = le corps brut de la requête, avant JSON.parse
function verifySignature(rawBody, receivedSig, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// Comparaison en temps constant (anti timing attack)
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(receivedSig)
);
}
Chaque fournisseur a ses conventions : Stripe utilise Stripe-Signature avec un timestamp et une tolérance anti-rejeu, GitHub utilise X-Hub-Signature-256. Utilisez toujours la bibliothèque officielle quand elle existe.
Les retries
Quand votre serveur répond mal (erreur 500, timeout, endpoint indisponible), l’émetteur réessaie. Stripe, en mode live, retente pendant jusqu’à trois jours avec un backoff exponentiel ; le Dashboard permet un renvoi manuel sur 15 jours, le Stripe CLI sur 30 jours. Un retry porte une nouvelle signature et un nouveau timestamp : ne vous en servez pas pour détecter un doublon.
L’idempotence
Conséquence directe des retries : le même événement peut arriver plusieurs fois. Votre traitement doit être idempotent, c’est-à-dire produire le même résultat qu’il soit exécuté une ou trois fois.
Le motif à retenir :
- Vérifier la signature.
- Enregistrer l’
idde l’événement avec une contrainte d’unicité en base. - Si l’id existe déjà → renvoyer
2xxsans retraiter. - Sinon → mettre en file d’attente et traiter, puis renvoyer
2xx.
Attention à ne pas confondre : la clé d’idempotence (Idempotency-Key) sert à vos requêtes POST vers une API comme Stripe, pour éviter de créer deux fois la même ressource. Elle ne remplace pas la déduplication des événements entrants.
Sécurité : les règles de base
- HTTPS obligatoire. Un webhook en clair expose les données et la signature.
- Vérifiez toujours la signature sur le corps brut, avec le secret propre à l’endpoint.
- Respectez la tolérance de timestamp du fournisseur pour limiter les attaques par rejeu.
- Ne faites jamais confiance au payload pour l’autorisation. Un montant ou un statut reçu par webhook ne remplace pas une vérification côté API avant une action sensible.
- Répondez vite en
2xx, puis traitez en asynchrone. Un traitement long fait expirer la requête et déclenche des retries inutiles. - Limitez l’exposition : un endpoint dédié par fournisseur, sans authentification utilisateur, et une allowlist d’IP si le fournisseur la publie.
- Ne loguez jamais les secrets ni le
whsec_…dans vos journaux.
Cas d’usage concrets (Stripe, Shopify, Make, n8n)
- Stripe –
checkout.session.completed,invoice.payment_failed: activer un accès, relancer un client en échec de paiement. - Shopify –
orders/create,orders/fulfilled: synchroniser une commande vers un ERP ou déclencher un email de suivi. - Make – le module Webhooks permet de recevoir une requête entrante et de démarrer un scénario. C’est le pont classique entre un service tiers et Make sans polling.
- n8n – le nœud Webhook joue le même rôle : il expose une URL, attend un GET ou un POST, et déclenche le workflow. Très pratique en auto-hébergement.
Le motif commun : un événement déclenche une action sans que vous ayez à écrire d’intégration propriétaire.
Comment tester un webhook
Trois méthodes, du plus simple au plus complet :
- Un service d’inspection type webhook.site : vous obtenez une URL temporaire, vous y envoyez vos requêtes et vous voyez en direct headers et payload. Idéal pour dessiner la structure d’un payload.
- Un tunnel local type ngrok : vous exposez
localhost:3000sur une URL publique et testez votre vrai code. - Le CLI du fournisseur :
stripe listen --forward-to localhost:3000/webhooks/striperedirige les événements vers votre machine, sans attendre un vrai paiement. On peut ensuite rejouer un événement depuis le Dashboard.
Testez systématiquement les cas moches : signature invalide, payload inattendu, événement en doublon, timeout de votre côté.
Questions fréquentes
Un webhook, c’est quoi en une phrase ?
C’est une notification HTTP automatique qu’une application envoie à une URL de votre choix dès qu’un événement se produit chez elle.
Quelle est la traduction française de webhook ?
Il n’existe pas de traduction officielle consacrée : « webhook » s’emploie tel quel en français. On rencontre parfois l’idée de « notification par rappel HTTP » ou « point d’écoute », mais ces formulations restent rares. Le terme technique reste « webhook ».
Webhook ou API, que choisir ?
Les deux ne s’opposent pas : une API sert à agir et à interroger l’historique, un webhook sert à recevoir un événement au moment où il se produit. On utilise généralement les deux ensemble.
Comment tester un webhook sans serveur ?
Avec un service d’inspection comme webhook.site, qui vous fournit une URL immédiate et affiche les requêtes reçues.
Pourquoi je reçois le même webhook plusieurs fois ?
Parce que l’émetteur a réessayé après un échec ou un timeout. C’est normal : gérez l’idempotence en enregistrant l’identifiant d’événement et en renvoyant 2xx pour les doublons.
Un webhook est-il sécurisé ?
Oui, à condition de vérifier la signature HMAC, d’utiliser HTTPS, de respecter la tolérance de timestamp et de ne jamais faire confiance au payload pour une décision sensible.
Sources
- MDN – HTTP messages (corps des requêtes)
- MDN – Méthode HTTP POST
- Stripe – Receive Stripe events in your webhook endpoint
- Stripe – Verify webhook signatures
- Stripe – Automatric retries
- Stripe – Idempotent requests
- GitHub – Webhook events and payloads


