Was du lernst
- Was Webhooks sind, warum Polling der falsche Instinkt ist und wie du Webhook-Signaturen verifizierst
- Idempotenz, Proration bei Upgrades mitten im Zyklus und Coupons versus Promotion Codes
- Embedded Checkout und die Webhook-Gotchas, die jeden Einsteiger erwischen
Überblick
In Teil 1 hast du eine Zahlung entgegengenommen. Aber hier ist die unbequeme Wahrheit: nachdem du einen Kunden zum Checkout umgeleitet hast, weiss deine App eigentlich nicht, was mit ihm passiert ist. Hat er gezahlt? Ist die Karte später fehlgeschlagen? Hat er nächsten Monat gekündigt? Zu raten ist, wie Leute das Produkt versehentlich gratis verschenken oder gekündigte Kunden weiter belasten. Die Antwort sind Webhooks: Stripe ruft deine App, wann immer etwas passiert, sodass deine App auf die Realität reagiert statt zu raten. Diese Lektion macht dein Billing vertrauenswürdig: verifizierte Webhooks, Idempotenz, Proration, Coupons, Embedded Checkout und die Gotchas, die jeden beim ersten Mal beissen.
Was du lernst
Du lernst, was ein Webhook ist und warum es der falsche Instinkt ist, Stripe zu pollen, wie du eine Webhook-Signatur verifizierst, sodass Angreifer keine Events fälschen können, wie Idempotenz dich davon abhält, ein dupliziertes Event doppelt zu verarbeiten, wie Proration eine Planänderung mitten im Zyklus fair handhabt, den Unterschied zwischen Coupons und Promotion Codes, wie Embedded Checkout funktioniert und die Webhook-Gotchas, die den klassischen "es funktionierte im Test, aber brach in Produktion"-Fehlschlag verursachen.
Voraussetzungen
Stripe Teil 1 und ein funktionierender Subscription-Checkout, plus ein Backend, das HTTP-Requests empfangen kann (deine Convex-Actions oder die Server-Routen deines Frameworks), denn ein Webhook ist einfach Stripe, das einen Request an dein Backend schickt. Die Secrets-Lektion auch, denn das Webhook-Signing-Secret ist ein weiterer Key, den es zu bewachen gilt.
Eine API ist eine Art, wie zwei Programme miteinander reden. Lerne, was eine API ist, wie sie funktioniert und warum sie für das Bauen mit KI zählt.
Eine .env-Datei speichert Secrets wie API Keys ausserhalb deines Codes, damit sie nie veröffentlicht werden. Lerne, was sie ist und wie du sie sicher hältst.
JSON, YAML und Markdown sind drei Klartext-Formate, denen du ständig begegnest. Lerne, wofür jedes da ist und wie du sie auf einen Blick liest.
Das Problem
Der naive Instinkt nach dem Checkout ist zu pollen: alle paar Sekunden Stripe fragen "haben sie schon gezahlt?". Das ist auf jeder Achse falsch. Es verschwendet Requests, es ist langsam, es verpasst Events, die später passieren (eine Verlängerung nächsten Monat, eine Karte, die in 30 Tagen fehlschlägt, eine Kündigung), und es skaliert nicht. Schlimmer, sich allein auf die Erfolgs-URL-Umleitung zu verlassen ist unzuverlässig - ein Kunde kann zahlen und dann den Tab schliessen, bevor die Umleitung feuert, und jetzt hat er gezahlt, aber deine App hat es nie aufgezeichnet. Das richtige Modell ist umgekehrt: frag nicht, lass dir sagen. Stripe pusht ein Event an dein Backend in dem Moment, in dem etwas passiert, und deine App reagiert. Das ist ein Webhook, und ihn richtig hinzubekommen ist, was Billing vertrauenswürdig macht.
Was Webhooks sind und Signaturen verifizieren
Ein Webhook ist Stripe, das einen HTTP-Request an eine URL macht, die dir gehört, wann immer ein Event auftritt: eine Zahlung ist gelungen, eine Subscription wurde verlängert, eine Karte ist fehlgeschlagen, eine Subscription wurde gekündigt. Dein Backend lauscht an dieser URL und aktualisiert deine Datenbank als Reaktion. Aber es gibt einen Haken: jeder im Internet könnte einen gefälschten Request an diese URL schicken und vorgeben, Stripe zu sein, und versuchen, deine App zu täuschen, kostenlosen Zugang zu gewähren. Also musst du verifizieren, dass jeder Request wirklich von Stripe kam. Stripe signiert jeden Webhook mit einem Secret (dem Webhook-Signing-Secret, das mit whsec_ beginnt), und du verifizierst diese Signatur bei jedem Request. Wenn die Verifizierung fehlschlägt, lehnst du den Request ab. Vertrau nie einem unverifizierten Webhook. Hier ist die Verifizierung, die nicht verhandelbar ist.
// Webhook-Handler (Backend). Verifiziere die Signatur, BEVOR du irgendetwas vertraust.
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() // MUSS der rohe Body sein, kein geparstes JSON
let event: Stripe.Event
try {
// Wirft, wenn die Signatur ungültig oder der Body verändert wurde.
event = await stripe.webhooks.constructEventAsync(rawBody, signature, webhookSecret)
} catch {
return new Response('Invalid signature', { status: 400 })
}
switch (event.type) {
case 'checkout.session.completed':
// Zugang gewähren / den Nutzer in deiner Datenbank als abonniert markieren.
break
case 'customer.subscription.deleted':
// Zugang entziehen - sie haben gekündigt.
break
case 'invoice.payment_failed':
// Den Nutzer warnen / einen Dunning-Flow starten.
break
}
return new Response('ok', { status: 200 })
}Ein Detail, das Leute bricht: du musst gegen den rohen Request-Body verifizieren, genau wie Stripe ihn gesendet hat. Wenn dein Framework den Body in JSON parst, bevor du verifizierst, passt die Signatur nicht und jeder Webhook schlägt fehl. Lies zuerst den rohen Body, verifiziere, dann parse.
Idempotenz: Duplikate sicher handhaben
Stripe garantiert, dass es jedes Event mindestens einmal zustellt, was heisst, es könnte dasselbe Event mehr als einmal zustellen - bei einem Retry nach einem Timeout oder einem Netzwerk-Schluckauf. Wenn dein Handler einen Monat Gratis-Credits gewährt oder eine Willkommens-E-Mail sendet, jedes Mal wenn er checkout.session.completed sieht, gewährt oder mailt eine doppelte Zustellung doppelt. Der Fix ist Idempotenz: gestalte deinen Handler so, dass das Verarbeiten desselben Events zweimal denselben Effekt hat wie das Verarbeiten einmal. Der einfachste verlässliche Ansatz ist, jede Event-ID aufzuzeichnen, die du verarbeitet hast, und sie zu überspringen, wenn du sie erneut siehst. Stripe empfiehlt auch, schnell mit einer 200 zu antworten und langsame Arbeit danach zu erledigen, damit Stripe nicht in einen Timeout läuft und unnötig erneut zustellt.
- Stripe stellt jedes Event mindestens einmal zu, also passieren Duplikate - gestalte dafür.
- Speichere verarbeitete Event-IDs; wenn du eine schon gesehen hast, quittiere sie und überspring die Arbeit.
- Mach Aktionen sicher zu wiederholen: "setze subscribed = true" ist natürlich idempotent; "füge einen Monat hinzu" ist es nicht.
- Antworte schnell mit 200; wenn du langsam bist oder einen Fehler hast, retryt Stripe, was mehr Duplikate verursacht.
Proration bei Upgrades mitten im Zyklus
Ein Kunde im monatlichen Plan upgradet zum jährlichen Plan, oder wechselt von einer günstigeren Stufe zu einer teureren, mitten in seinem Abrechnungszeitraum. Was soll er zahlen? Proration ist Stripe, das den fairen Betrag berechnet: es schreibt den ungenutzten Teil dessen gut, was er schon gezahlt hat, und berechnet die Differenz für den neuen Plan für den Rest des Zeitraums. Du berechnest das nicht selbst - du sagst Stripe, die Subscription auf den neuen Preis zu aktualisieren, und Stripe ermittelt die anteilige Abbuchung oder Gutschrift. Dein Job ist, das Verhalten zu entscheiden (die Differenz sofort berechnen oder auf die nächste Rechnung anwenden) und den Zugang in deiner App zu aktualisieren, wenn der Webhook die Änderung bestätigt. Der zu vermeidende Fehler ist, anteilige Beträge von Hand zu berechnen; lass Stripe die Arithmetik machen und reagiere auf die resultierenden Events.
- Upgrade mitten im Zyklus: Stripe schreibt die ungenutzte Zeit gut und berechnet die anteilige Differenz.
- Du aktualisierst die Subscription auf die neue Preis-ID; Stripe berechnet das Geld.
- Wähle, ob die Proration jetzt berechnet oder auf die nächste Rechnung addiert wird.
- Reagiere auf den Webhook (Subscription aktualisiert, Rechnung bezahlt), um den Zugang in deiner App zu synchronisieren.
Coupons und Promotion Codes
Diese zwei sind verwandt, aber nicht dasselbe, und die Unterscheidung zählt. Ein Coupon ist die zugrunde liegende Rabattregel: "20 Prozent Rabatt" oder "10 USD Rabatt, einmalig". Ein Promotion Code ist ein kundenseitiger Code (wie LAUNCH20), der einen Coupon anwendet. Du erstellst einen Coupon, dann erstellst du einen oder mehrere Promotion Codes, die auf ihn zeigen. Der Grund für zwei Schichten ist Flexibilität: ein Coupon ("20 Prozent Rabatt") kann mehrere Codes mit verschiedenen Einschränkungen tragen (Ablauf, Nutzungslimit, nur Erstkunden). Im Checkout kannst du ein Promotion-Code-Feld aktivieren, sodass Kunden selbst einen Code tippen, oder du kannst einen Coupon direkt auf eine Session im Code anwenden für einen automatischen Rabatt. Nutze Promotion Codes für Marketing-Kampagnen, bei denen Kunden eingeben sollen, und direkte Coupon-Anwendung für Rabatte, die du programmatisch gewährst.
- Coupon: die Rabattregel selbst (Prozent Rabatt, fester Betrag, Dauer).
- Promotion Code: ein kundenseitiger Code (LAUNCH20), der einen Coupon anwendet.
- Ein Coupon kann viele Promotion Codes mit verschiedenen Limits und Abläufen tragen.
- Aktiviere das Promotion-Code-Feld im Checkout für kundeneingegebene Codes, oder wende einen Coupon im Code an für automatische Rabatte.
Embedded Checkout
Teil 1 erwähnte Embedded Checkout; hier verdient es seinen Platz. Embedded Checkout rendert Stripes sicheren Zahlungs-Flow innerhalb deiner eigenen Seite, statt zu einer von Stripe gehosteten URL umzuleiten, sodass der Kunde die ganze Zeit auf deiner Domain bleibt. Das hebt meist die Conversion (jede Umleitung ist eine Chance, jemanden zu verlieren) und fühlt sich mehr wie Teil deines Produkts an. Die Mechanik: dein Backend erstellt eine Checkout-Session im embedded ui_mode und gibt ein Client Secret zurück, und dein Frontend mountet Stripes Embedded-Komponente mit diesem Secret. Die Karte wird weiterhin vollständig von Stripe eingesammelt, sodass du alle Sicherheits- und Compliance-Vorteile behältst, während du die Erfahrung besitzt. Greif zu Embedded, sobald dein Hosted-Flow funktioniert und du die Conversion straffen willst.
// Backend: erstelle eine EMBEDDED Checkout-Session und gib ihr Client Secret zurück.
const session = await stripe.checkout.sessions.create({
ui_mode: 'embedded', // embedded, keine Umleitung
mode: 'subscription',
line_items: [{ price: priceId, quantity: 1 }],
return_url: 'https://app.yoursite.com/welcome?session_id={CHECKOUT_SESSION_ID}',
})
// Sende session.client_secret an das Frontend, das Stripes Embedded-UI mountet.Häufige Webhook-Gotchas
Das sind die konkreten Dinge, die Webhooks "im Test funktionieren, aber in Produktion brechen" lassen, gesammelt, damit du jedem ausweichen kannst. Die meisten Produktions-Billing-Bugs lassen sich auf einen davon zurückführen.
- Den Body vor dem Verifizieren parsen: die Signaturprüfung braucht den rohen Body. Lies zuerst roh, verifiziere, dann parse.
- Das falsche Webhook-Secret nutzen: Test und Live haben jeweils ein anderes whsec_, und die Stripe CLI gibt ein drittes fürs lokale Weiterleiten. Passe das Secret an die Umgebung an.
- Duplikate nicht handhaben: ohne Idempotenz verarbeitet ein erneut zugestelltes Event doppelt. Speichere verarbeitete Event-IDs.
- Der Erfolgs-Umleitung statt dem Webhook vertrauen: der Nutzer kann den Tab schliessen, bevor die Umleitung feuert, also gewähre Zugang aus dem Webhook, nicht der Umleitung.
- Langsame oder fehlschlagende Handler: gib schnell 200 zurück. Wenn du zu lange brauchst oder einen Fehler hast, retryt Stripe, was Duplikate vervielfacht.
- Vergessen, den Produktions-Webhook-Endpoint zu registrieren: der Test-Endpoint überträgt sich nicht in den Live-Modus. Füg die Live-URL und ihr Secret hinzu, wenn du live gehst.
# Webhooks lokal mit der Stripe CLI testen - sie leitet Live-Events an deinen Rechner weiter
# und gibt ein whsec_-Secret zur lokalen Verifizierung aus.
stripe login
stripe listen --forward-to localhost:5296/api/stripe/webhook
# In einem anderen Terminal ein Fake-Event auslösen, um deinen Handler zu testen:
stripe trigger checkout.session.completedTypische Fehler
Über die Gotcha-Liste hinaus: Stripe pollen, statt Webhooks zu nutzen; einem unverifizierten Webhook vertrauen und einen Angreifer ein "Zahlung gelungen"-Event fälschen lassen, um kostenlosen Zugang zu bekommen; Proration von Hand berechnen, statt Stripe es machen zu lassen; Coupons und Promotion Codes verwechseln; und Embedded Checkout shippen, ohne zuerst den Hosted-Flow und die Webhooks solide zu bekommen. Der rote Faden: lass dir sagen, frag nicht; verifiziere alles; lass Stripe die Geld-Mathematik machen; und behandle Duplikate als unvermeidlich.
Business-ROI
Diese Lektion ist der Unterschied zwischen Billing, das dir still Geld verliert, und Billing, dem du vertrauen kannst. Ohne verifizierte Webhooks verschenkst du entweder Zugang, für den du nie bezahlt wurdest, oder belastest weiter Leute, die gekündigt haben - beides ist direkter Umsatz- und Reputationsschaden. Idempotenz verhindert die peinliche Doppelabbuchung oder Doppelgewährung. Von Stripe gemachte Proration hält Upgrades fair, was Reibung aus der wertvollsten Aktion nimmt, die ein Kunde tun kann: mehr ausgeben. Und Embedded Checkout plus Promotion Codes sind direkte Conversion-Hebel. Einen Tag aufzuwenden, um Webhooks richtig zu bekommen, schützt jeden Dollar, der von hier an durch dein Produkt fliesst.
Checkliste
Dein Billing ist produktionsreif, wenn all das gilt.
- Du verifizierst jede Webhook-Signatur gegen den rohen Body, bevor du darauf reagierst.
- Dein Handler ist idempotent - ein dupliziertes Event verursacht keine Doppelverarbeitung.
- Du gewährst und entziehst Zugang aus Webhooks, nicht aus der Erfolgs-Umleitung.
- Upgrades nutzen Stripe-Proration, und du hast einen funktionierenden Promotion Code und einen Embedded- oder Hosted-Flow live.
Ressourcen
Die Stripe-Webhooks-Docs, die Events-Referenz und die Stripe CLI sind hier deine Kernwerkzeuge - die CLI macht besonders die lokale Webhook-Entwicklung schmerzlos. Halt deine Test- und Live-Webhook-Signing-Secrets klar beschriftet, damit du sie nie kreuzt. Als Nächstes nimmt die letzte Lektion den ganzen Stack live: Dev-zu-Prod-Migration für Clerk und Convex, DNS, Cloudflare, Search Console und Performance.
Deine Aufgabe
Installiere die Stripe CLI, führe stripe listen aus, um Events an dein lokales Backend weiterzuleiten, und baue einen Webhook-Handler, der die Signatur verifiziert und bei checkout.session.completed Zugang gewährt. Löse ein Duplikat desselben Events aus und bestätige, dass dein Handler es nicht doppelt verarbeitet. Aktiviere dann ein Promotion-Code-Feld in deinem Checkout und teste einen Coupon. Du hast jetzt Billing, das auf die Realität reagiert, statt zu raten.
Nächste Lektion
Dein Produkt nimmt Zahlungen verlässlich entgegen. Die letzte Lektion des Kurses nimmt alles live: Clerk und Convex von Dev zu Prod migrieren, DNS und Cloudflare verdrahten, deine Seite in der Google Search Console verifizieren und deine Sitemap einreichen und deine Lighthouse-Scores ins Grüne bringen, damit das Live-Produkt schnell und auffindbar ist.

Kommentare
Kommentare werden geladen.
Kommentar schreiben