Ce que tu apprends
- Ce que sont les webhooks, pourquoi le polling est le mauvais instinct et comment vérifier les signatures de webhook
- L'idempotence, la proration sur les upgrades en milieu de cycle et coupons versus promotion codes
- L'Embedded Checkout et les pièges de webhook qui attrapent chaque débutant
Vue d'ensemble
En partie 1, vous avez encaissé un paiement. Mais voici la vérité inconfortable : après avoir redirigé un client vers le checkout, votre app ne sait pas vraiment ce qui lui est arrivé. A-t-il payé ? La carte a-t-elle échoué plus tard ? A-t-il annulé le mois suivant ? Deviner, c'est ainsi que les gens offrent le produit gratuitement par accident ou continuent de facturer des clients annulés. La réponse, ce sont les webhooks : Stripe appelle votre app chaque fois qu'il se passe quelque chose, pour que votre app réagisse à la réalité au lieu de deviner. Cette leçon rend votre facturation digne de confiance : webhooks vérifiés, idempotence, proration, coupons, Embedded Checkout et les pièges qui mordent tout le monde la première fois.
Ce que vous allez apprendre
Vous allez apprendre ce qu'est un webhook et pourquoi poller Stripe est le mauvais instinct, comment vérifier une signature de webhook pour que les attaquants ne puissent pas falsifier des events, comment l'idempotence vous empêche de traiter un event dupliqué deux fois, comment la proration gère équitablement un changement de plan en milieu de cycle, la différence entre coupons et promotion codes, comment fonctionne l'Embedded Checkout et les pièges de webhook qui causent le classique échec « ça marchait en test mais a cassé en production ».
Prérequis
Stripe partie 1 et un subscription checkout fonctionnel, plus un backend qui peut recevoir des requêtes HTTP (vos actions Convex ou les routes serveur de votre framework), car un webhook n'est que Stripe qui envoie une requête à votre backend. La leçon sur les secrets aussi, car le webhook signing secret est une autre clé à garder.
Le problème
L'instinct naïf après le checkout est de poller : demander à Stripe toutes les quelques secondes « ont-ils payé déjà ? ». C'est faux sur tous les axes. Cela gaspille des requêtes, c'est lent, cela rate les events qui arrivent plus tard (un renouvellement le mois prochain, une carte qui échoue dans 30 jours, une annulation) et cela ne passe pas à l'échelle. Pire, se reposer uniquement sur la redirection de l'URL de succès est peu fiable - un client peut payer puis fermer l'onglet avant que la redirection ne se déclenche, et maintenant il a payé mais votre app ne l'a jamais enregistré. Le bon modèle est inversé : ne demandez pas, faites-vous dire. Stripe pousse un event à votre backend à l'instant où quelque chose arrive, et votre app réagit. C'est un webhook, et bien le faire est ce qui rend la facturation digne de confiance.
Ce que sont les webhooks et vérifier les signatures
Un webhook est Stripe qui fait une requête HTTP vers une URL qui vous appartient, chaque fois qu'un event survient : un paiement a réussi, une subscription a été renouvelée, une carte a échoué, une subscription a été annulée. Votre backend écoute à cette URL et met à jour votre base de données en réaction. Mais il y a un piège : n'importe qui sur internet pourrait envoyer une fausse requête à cette URL en prétendant être Stripe, et essayer de tromper votre app pour accorder un accès gratuit. Donc vous devez vérifier que chaque requête vient vraiment de Stripe. Stripe signe chaque webhook avec un secret (le webhook signing secret, qui commence par whsec_), et vous vérifiez cette signature à chaque requête. Si la vérification échoue, vous rejetez la requête. Ne faites jamais confiance à un webhook non vérifié. Voici la vérification qui n'est pas négociable.
// Webhook handler (backend). Vérifiez la signature AVANT de faire confiance à quoi que ce soit.
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET! // whsec_xxx
export async function handleStripeWebhook(req: Request) {
const signature = req.headers.get('stripe-signature')!
const rawBody = await req.text() // DOIT être le body brut, pas du JSON parsé
let event: Stripe.Event
try {
// Lance une erreur si la signature est invalide ou si le body a été altéré.
event = await stripe.webhooks.constructEventAsync(rawBody, signature, webhookSecret)
} catch {
return new Response('Invalid signature', { status: 400 })
}
switch (event.type) {
case 'checkout.session.completed':
// Accorder l'accès / marquer l'utilisateur comme abonné dans votre base de données.
break
case 'customer.subscription.deleted':
// Retirer l'accès - ils ont annulé.
break
case 'invoice.payment_failed':
// Avertir l'utilisateur / démarrer un flux de relance.
break
}
return new Response('ok', { status: 200 })
}Un détail qui casse les gens : vous devez vérifier contre le body brut de la requête, exactement comme Stripe l'a envoyé. Si votre framework parse le body en JSON avant que vous ne vérifiiez, la signature ne correspond pas et chaque webhook échoue. Lisez d'abord le body brut, vérifiez, puis parsez.
Idempotence : gérer les doublons en sécurité
Stripe garantit qu'il livre chaque event au moins une fois, ce qui veut dire qu'il pourrait livrer le même event plus d'une fois - lors d'une nouvelle tentative après un timeout ou un hoquet réseau. Si votre handler accorde un mois de crédits gratuits ou envoie un e-mail de bienvenue chaque fois qu'il voit checkout.session.completed, une double livraison accorde ou envoie en double. Le fix est l'idempotence : concevez votre handler pour que traiter le même event deux fois ait le même effet que le traiter une fois. L'approche fiable la plus simple est d'enregistrer chaque ID d'event que vous avez traité et de le sauter si vous le revoyez. Stripe recommande aussi de répondre vite avec un 200 et de faire le travail lent après, pour que Stripe ne tombe pas en timeout et ne relivre pas inutilement.
- Stripe livre chaque event au moins une fois, donc les doublons arrivent - concevez pour ça.
- Stockez les IDs d'events traités ; si vous en avez déjà vu un, acquittez-le et sautez le travail.
- Rendez les actions sûres à répéter : « poser subscribed = true » est naturellement idempotent ; « ajouter un mois » ne l'est pas.
- Répondez vite avec 200 ; si vous êtes lent ou avez une erreur, Stripe retente, ce qui cause plus de doublons.
Proration sur les upgrades en milieu de cycle
Un client sur le plan mensuel upgrade vers le plan annuel, ou passe d'un palier moins cher à un plus cher, en plein milieu de sa période de facturation. Que doit-il payer ? La proration est Stripe qui calcule le montant juste : il crédite la portion inutilisée de ce qu'il a déjà payé et facture la différence pour le nouveau plan sur le reste de la période. Vous ne calculez pas cela vous-même - vous dites à Stripe de mettre à jour la subscription vers le nouveau prix, et Stripe détermine le débit ou crédit proratisé. Votre job est de décider le comportement (facturer la différence tout de suite ou l'appliquer à la prochaine facture) et de mettre à jour l'accès dans votre app quand le webhook confirme le changement. L'erreur à éviter est de calculer les montants proratisés à la main ; laissez Stripe faire l'arithmétique et réagissez aux events résultants.
- Upgrade en milieu de cycle : Stripe crédite le temps inutilisé et facture la différence proratisée.
- Vous mettez à jour la subscription vers le nouvel ID de prix ; Stripe calcule l'argent.
- Choisissez si la proration est facturée maintenant ou ajoutée à la prochaine facture.
- Réagissez au webhook (subscription mise à jour, facture payée) pour synchroniser l'accès dans votre app.
Coupons et promotion codes
Ces deux-là sont liés mais pas identiques, et la distinction compte. Un coupon est la règle de rabais sous-jacente : « 20 pour cent de réduction » ou « 10 USD de réduction, une fois ». Un promotion code est un code côté client (comme LAUNCH20) qui applique un coupon. Vous créez un coupon, puis vous créez un ou plusieurs promotion codes qui pointent vers lui. La raison des deux couches est la flexibilité : un coupon (« 20 pour cent de réduction ») peut porter plusieurs codes avec différentes restrictions (expiration, limite d'usage, nouveaux clients seulement). Dans le checkout, vous pouvez activer un champ promotion code pour que les clients tapent un code eux-mêmes, ou vous pouvez appliquer un coupon directement à une session dans le code pour un rabais automatique. Utilisez les promotion codes pour les campagnes marketing où les clients doivent saisir, et l'application directe de coupon pour les rabais que vous accordez programmatiquement.
- Coupon : la règle de rabais elle-même (pourcentage, montant fixe, durée).
- Promotion code : un code côté client (LAUNCH20) qui applique un coupon.
- Un coupon peut porter de nombreux promotion codes avec différentes limites et expirations.
- Activez le champ promotion code dans le checkout pour des codes saisis par le client, ou appliquez un coupon dans le code pour des rabais automatiques.
Embedded Checkout
La partie 1 mentionnait l'Embedded Checkout ; ici il mérite sa place. L'Embedded Checkout rend le flux de paiement sécurisé de Stripe à l'intérieur de votre propre page, au lieu de rediriger vers une URL hébergée par Stripe, si bien que le client reste sur votre domaine tout du long. Cela augmente généralement la conversion (chaque redirection est une occasion de perdre quelqu'un) et se sent davantage comme une partie de votre produit. La mécanique : votre backend crée une checkout session en ui_mode embedded et renvoie un client secret, et votre frontend monte le composant Embedded de Stripe avec ce secret. La carte est toujours entièrement collectée par Stripe, donc vous gardez tous les avantages de sécurité et de conformité tout en possédant l'expérience. Saisissez Embedded une fois que votre flux Hosted fonctionne et que vous voulez resserrer la conversion.
// Backend : créer une checkout session EMBEDDED et renvoyer son client secret.
const session = await stripe.checkout.sessions.create({
ui_mode: 'embedded', // embedded, pas de redirection
mode: 'subscription',
line_items: [{ price: priceId, quantity: 1 }],
return_url: 'https://app.yoursite.com/welcome?session_id={CHECKOUT_SESSION_ID}',
})
// Envoyez session.client_secret au frontend, qui monte l'UI Embedded de Stripe.Pièges de webhook fréquents
Voici les choses concrètes qui font que les webhooks « marchent en test mais cassent en production », rassemblées pour que vous puissiez éviter chacune. La plupart des bugs de facturation de production se ramènent à l'un d'eux.
- Parser le body avant de vérifier : la vérification de signature a besoin du body brut. Lisez brut d'abord, vérifiez, puis parsez.
- Utiliser le mauvais webhook secret : test et live ont chacun un whsec_ différent, et la Stripe CLI en donne un troisième pour le forwarding local. Adaptez le secret à l'environnement.
- Ne pas gérer les doublons : sans idempotence, un event relivré traite en double. Stockez les IDs d'events traités.
- Faire confiance à la redirection de succès plutôt qu'au webhook : l'utilisateur peut fermer l'onglet avant que la redirection ne se déclenche, donc accordez l'accès depuis le webhook, pas la redirection.
- Handlers lents ou en échec : renvoyez 200 vite. Si vous mettez trop de temps ou avez une erreur, Stripe retente, ce qui multiplie les doublons.
- Oublier d'enregistrer le webhook endpoint de production : l'endpoint de test ne se transfère pas en mode live. Ajoutez l'URL live et son secret quand vous passez en live.
# Tester les webhooks en local avec la Stripe CLI - elle forwarde les events live vers votre machine
# et donne un secret whsec_ pour la vérification locale.
stripe login
stripe listen --forward-to localhost:5296/api/stripe/webhook
# Dans un autre terminal, déclencher un faux event pour tester votre handler :
stripe trigger checkout.session.completedErreurs fréquentes
Au-delà de la liste des pièges : poller Stripe au lieu d'utiliser des webhooks ; faire confiance à un webhook non vérifié et laisser un attaquant falsifier un event « paiement réussi » pour obtenir un accès gratuit ; calculer la proration à la main au lieu de laisser Stripe le faire ; confondre coupons et promotion codes ; et shipper l'Embedded Checkout sans d'abord solidifier le flux Hosted et les webhooks. Le fil rouge : faites-vous dire, ne demandez pas ; vérifiez tout ; laissez Stripe faire les maths d'argent ; et traitez les doublons comme inévitables.
ROI business
Cette leçon est la différence entre une facturation qui vous fait perdre de l'argent en silence et une facturation à laquelle vous pouvez faire confiance. Sans webhooks vérifiés, soit vous offrez un accès pour lequel vous n'avez jamais été payé, soit vous continuez de facturer des gens qui ont annulé - les deux sont des dégâts directs de revenu et de réputation. L'idempotence empêche le double débit ou double octroi embarrassant. La proration faite par Stripe garde les upgrades justes, ce qui retire de la friction de l'action la plus précieuse qu'un client peut faire : dépenser plus. Et l'Embedded Checkout plus les promotion codes sont des leviers de conversion directs. Consacrer un jour à bien faire les webhooks protège chaque dollar qui coule à travers votre produit à partir d'ici.
Checklist
Votre facturation est prête pour la production quand tout ceci tient.
- Vous vérifiez chaque signature de webhook contre le body brut avant d'y réagir.
- Votre handler est idempotent - un event dupliqué ne cause pas de double traitement.
- Vous accordez et retirez l'accès depuis les webhooks, pas depuis la redirection de succès.
- Les upgrades utilisent la proration Stripe, et vous avez un promotion code fonctionnel et un flux Embedded ou Hosted en live.
Ressources
Les docs webhooks de Stripe, la référence des events et la Stripe CLI sont vos outils clés ici - la CLI rend surtout le development local de webhooks indolore. Gardez vos webhook signing secrets test et live clairement étiquetés, pour ne jamais les croiser. Ensuite, la dernière leçon passe tout le stack en live : migration dev-vers-prod pour Clerk et Convex, DNS, Cloudflare, Search Console et performance.
Votre mission
Installez la Stripe CLI, lancez stripe listen pour forwarder les events vers votre backend local, et bâtissez un webhook handler qui vérifie la signature et accorde l'accès sur checkout.session.completed. Déclenchez un doublon du même event et confirmez que votre handler ne le traite pas deux fois. Activez ensuite un champ promotion code dans votre checkout et testez un coupon. Vous avez maintenant une facturation qui réagit à la réalité, au lieu de deviner.
Prochaine leçon
Votre produit encaisse les paiements de façon fiable. La dernière leçon du cours passe tout en live : migrer Clerk et Convex du dev à la prod, câbler DNS et Cloudflare, vérifier votre site dans la Google Search Console et soumettre votre sitemap, et amener vos scores Lighthouse au vert, pour que le produit live soit rapide et trouvable.

Commentaires
Chargement des commentaires.
Poster un commentaire