DEV Community

Usama Habib
Usama Habib

Posted on • Originally published at osamahabib.com

How to Add Stripe Subscription Billing to Next.js 15 (Webhooks Done Right)

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`,
});`
Enter fullscreen mode Exit fullscreen mode

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!
);`
Enter fullscreen mode Exit fullscreen mode

Gate features in one line

Once your DB knows who's paying:

`if (!user.isPro) redirect("/pricing");`
Enter fullscreen mode Exit fullscreen mode

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:

👉 osamahabib.com - Stripe Billing Next.js 15

Top comments (0)