Retour à l'académie
Ingénierie11 min de lecture

Comment fonctionnent les webhooks Stripe (et comment ne pas exploser en prod)

L'architecture, les modes de défaillance et les patterns qu'on aurait aimé connaître au jour 1.

Les webhooks sont le moyen pour Stripe d'informer votre application des événements : abonnements démarrant, factures payées, cartes échouant. Le concept est simple — Stripe POST du JSON sur votre endpoint — mais les modes de défaillance (retries, ordre, signatures, idempotence) causent plus d'incidents en prod que tout autre sujet Stripe. Voici le guide de survie.

Étape 1

Ce qu'est vraiment un webhook

STRIPEENDPOINTQUEUEWORKERDATABASEStripeevent POST/webhooksverify sigreturn 200QueueFIFO bufferWorkerre-fetch fromStripe APIDBHTTPS + signatureack < 30sasync, retryableidempotent writeOn non-2xx → Stripe retries with same event idup to 3 days · exponential backoff · same payload
Stripe → verified endpoint → queue → worker → database.

Quand quelque chose se passe dans votre compte Stripe (ex. paiement client réussi), Stripe envoie un POST HTTP sur une URL que vous avez configurée, avec un JSON décrivant l'événement. Votre serveur lit l'événement et met à jour votre base. C'est tout le concept. La complexité est dans les cas limites.

Étape 2

Vérifier la signature à chaque requête

Stripe signe chaque webhook avec un secret. Si vous ne vérifiez pas la signature, n'importe qui sur Internet peut POST sur votre endpoint et forger des événements. Utilisez le SDK officiel : stripe.webhooks.constructEvent(rawBody, signature, secret). Si ça throw, retournez 400 — ne traitez jamais d'événement non signé.

Attention

La vérification de signature exige le body brut, non parsé. Beaucoup de frameworks parsent le JSON avant les middlewares — désactivez le parsing JSON pour la route webhook spécifiquement.

Étape 3

S'abonner uniquement aux événements utiles

Stripe a des centaines de types d'événements. Abonnez-vous à un sous-ensemble ciblé : customer.subscription.created/updated/deleted, invoice.paid, invoice.payment_failed, customer.updated. Évitez 'tous les événements' — votre endpoint sera bombardé de bruit et vous traiterez des événements que vous ne comprenez pas.

Étape 4

Acquitter vite, traiter en async

Stripe attend une réponse 2xx en moins de 30 secondes, sinon il considère la livraison échouée et réessaye. Votre handler doit : (1) vérifier la signature, (2) pousser l'événement dans une queue, (3) retourner 200 immédiatement. Faites le vrai travail base en worker. Cela isole Stripe de votre latence et évite les tempêtes de retry.

Astuce

Pas de queue ? Écrivez l'événement brut dans une table 'pending_webhook' et traitez-la en cron. Même effet.

Étape 5

Gérer l'idempotence

DELIVERY #1evt_1ABC123invoice.paidDELIVERY #2 (RETRY)evt_1ABC123same idINSERT INTO events(id, type, payload)ON CONFLICT (id) DO NOTHINGFIRST WRITE✓ row createdhandler runs onceDUPLICATE⤴ skippedno double-chargeThe unique index on event id is your idempotency key.Cheaper than locks, simpler than queues, harder to get wrong.
Same event id arriving twice → unique index makes the second a no-op.

Stripe livre le même événement plusieurs fois dans deux cas : (1) votre endpoint a renvoyé non-2xx et Stripe réessaie, (2) redélivraison interne sur défaillance rare. Chaque événement a un champ `id` immuable (ex. evt_1ABC123). Avant traitement, vérifiez si vous avez déjà vu cet id. Pattern le plus simple : index unique sur event_id dans votre table — INSERT ON CONFLICT DO NOTHING.

Étape 6

Ne faites pas confiance à l'ordre

Les webhooks peuvent arriver dans le désordre. customer.subscription.updated peut arriver avant customer.subscription.created si Stripe réessaie le premier. Re-fetch toujours l'objet via l'API Stripe dans votre handler — ne faites jamais confiance au seul payload de l'événement. L'événement dit 'quelque chose a changé', l'API donne la vérité actuelle.

Attention

Construire la logique métier sur l'hypothèse d'un ordre causal des événements finira par casser. Anticipez.

Étape 7

Construire un outil de replay

Tôt ou tard, un bug fera sauter des événements. Vous devrez les rejouer. Le dashboard Stripe a un bouton 'Renvoyer' par événement, mais pour le bulk vous voudrez un script qui lit votre propre table de log et rejoue le handler. Construisez-le avant d'en avoir besoin.

Étape 8

Surveiller le taux d'échec

Mettez une alerte quand votre handler retourne non-2xx plus de 1 % du temps sur une heure. Stripe finit par désactiver un endpoint qui échoue trop. Un seul bug peut détruire silencieusement votre sync si personne ne regarde.

Les webhooks sont trompeusement simples au début et impitoyables à opérer. Vérifiez les signatures, queue puis traitement, dédupliquez par event id, re-fetch via l'API, surveillez le taux d'échec. Faites ces cinq choses bien et vous n'aurez jamais d'incident Stripe à 3h du matin.

Données Stripe, sans le pipeline

FlowMRR gère l'ingestion des webhooks, l'idempotence et le re-fetch pour vous. Connectez Stripe et les métriques arrivent.

Utiliser le pipeline FlowMRR

Questions

Faut-il gérer chaque événement envoyé par Stripe ?+

Non. Abonnez-vous uniquement aux événements qui intéressent votre app. Les non-abonnés ne sont pas livrés, votre endpoint reste calme et votre code focus.

Quelle architecture de base pour les webhooks ?+

Une table `webhook_events` avec (id PRIMARY KEY = event id Stripe, type, payload JSONB, received_at, processed_at). Traitez depuis cette table. La contrainte unique sur id gère la déduplication ; processed_at permet de rejouer sans effort.

Exposer les webhooks derrière un CDN ou directement ?+

Directement, sur une route séparée de l'API principale. Les CDN peuvent bufferiser ou modifier les bodies, cassant la signature. Beaucoup (CloudFlare notamment) ont un réglage de bypass documenté pour les webhooks Stripe.