Most Stripe tutorials stop at "redirect to Checkout." That's the easy part. The part that actually matters - webhooks - is what keeps getting skipped.
This is the same billing flow I use in production SaaS apps.
👉 Full guide with copy-paste code
What you'll build
A complete subscription loop: user clicks Subscribe → Stripe Checkout → pays → your database reliably knows they're a paying
customer - even if they close the tab immediately after payment.
The part most tutorials miss
`// metadata is how you link a Stripe payment to YOUR user
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: "price_XXXXXXXX", quantity: 1 }],
metadata: { userId }, // ← most tutorials skip this
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
});`
Without metadata.userId, your webhook has no way to know which user just paid.
Two webhook mistakes that break everything
1. Parsing body as JSON - you must use req.text() for raw body, otherwise signature verification fails.
2. Skipping signature verification - without it, anyone can fake a "user paid" event to your endpoint.
`const body = await req.text(); // NOT req.json()
const event = stripe.webhooks.constructEvent(
body,
req.headers.get("stripe-signature")!,
process.env.STRIPE_WEBHOOK_SECRET!
);`
Gate features in one line
Once your DB knows who's paying:
`if (!user.isPro) redirect("/pricing");`
That's the whole loop: checkout → webhook → DB → gate.
Full guide covers all 6 steps, local webhook testing with Stripe CLI, common mistakes, and FAQ:













