← Back to blog
TutorialPublished 2026-04-28· 10 min read

How to Add Stripe Subscriptions to Next.js (Complete Guide)

By make1day · Updated 2026-11-07

This is the shortest path to Stripe subscriptions in Next.js that will not break in production. Four moving parts, no fluff.

1. Create the product and price

In the Stripe dashboard create a Product with a recurring Price. Copy the Price ID into env as STRIPE_PRICE_ID_PRO. Never hard-code it in source.

2. The checkout route

A POST /api/checkout route reads the plan, resolves the price id, and creates a Checkout Session with mode subscription, a client_reference_id for your user, and explicit success_url and cancel_url. Return the hosted URL to the browser. Require a signed-in user before creating the session — guest checkout leaves no one to grant the entitlement to.

3. The webhook is the source of truth

Verify the raw body against the signature header BEFORE parsing JSON. Handle checkout.session.completed and customer.subscription.deleted, then update your database. Two rules:

  • Verification failure (missing header, bad HMAC, malformed JSON) must throw and produce HTTP 400. Acknowledging a forged request with 200 is a security bug.
  • Record processed event ids and skip replays before any write. Stripe retries; without this you get double upgrades and phantom refunds.

4. The feature gate

On every protected route or server action, read the plan from the database — never from client state. If the plan is inactive, return 402. Your AI endpoint, download ZIP or seat limits all hang off this one check.

5. Test the whole loop

Use the Stripe test card 4242 4242 4242 4242 in test mode: complete checkout, confirm the plan flips, cancel, confirm it flips back. Then send a webhook with a corrupted signature and confirm 400. If the loop works with one env file, your billing is real.

A good boilerplate hands you steps 2–4 already wired, and keeps them behind a provider-agnostic interface so adding Creem, PayPal, Waffo or Lemon Squeezy later does not rewrite your app.

FAQ

Should I parse the webhook body before verifying it?+

No. Verify the exact raw bytes against the signature header first, then parse. Parsing first and re-serialising changes the bytes and breaks HMAC verification.

Why does guest checkout break entitlement?+

The webhook grants access by user id or payment email. If nobody is signed in and the payment email does not match an account, the license has no owner to claim it. Require login before creating the session.

What does an ignored webhook response mean?+

200 with an ignored flag means the event was validly signed but is not one we handle. It must never be used for verification failures, which are 400.

All frequently asked questions · Markdown version (for AI agents)

How to Add Stripe Subscriptions to Next.js (Complete Guide) · make1day