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.
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
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.
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.