How to add Stripe to Next.js in 5 minutes 💰

June 13, 2025

What we'll do

We'll build a simple Next.js app that allows users to buy a product in around 5 minutes with Stripe.

What we'll do:

  • use Stripe Checkout to handle the payment.
  • use the Stripe Dashboard to create a product and price.
  • use the Stripe API to create a checkout session.

Let's go 🚀 I'll use pnpm for the commands. Feel free to use your favorite package manager.

1. Install Stripe

pnpm add stripe @stripe/stripe-js

2. Environment variables

Create .env (or .env.local):

# Stripe
STRIPE_SECRET_KEY=sk_test_…
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_…
STRIPE_WEBHOOK_SECRET=whsec_… # filled after step 6
# App base URL (used in redirect links)
NEXT_PUBLIC_APP_URL=http://localhost:3000

3. Add your Checkout session API route

  • One endpoint, one Stripe call. No cookies, no auth required.

src/app/api/checkout/route.ts

import Stripe from 'stripe'
import { NextResponse } from 'next/server'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
})
export async function POST(req: Request) {
const { priceId } = await req.json()
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cancel`,
})
return NextResponse.json({ url: session.url })
}

4. Add a client helper

getStripe.ts (already in the starter):

import { loadStripe } from '@stripe/stripe-js'
let stripePromise: ReturnType<typeof loadStripe>
export const getStripe = () => {
if (!stripePromise)
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!)
return stripePromise
}

5. Redirect button

'use client'
import { useState } from 'react'
import { getStripe } from '@/getStripe'
export default function PayButton({ priceId }: { priceId: string }) {
const [loading, setLoading] = useState(false)
const handleClick = async () => {
setLoading(true)
const res = await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ priceId }),
})
const { url } = await res.json()
;(await getStripe())!.redirectToCheckout({ url })
}
return (
<button onClick={handleClick} disabled={loading}>
{loading ? 'Redirecting…' : 'Buy'}
</button>
)
}

6. Add a webhook endpoint

This is necessary for Stripe to tell you when a payment is successful. This lets you mark the order as paid in your database and unlock any functionality for the user.

src/app/api/webhook/route.ts

import { NextRequest, NextResponse } from 'next/server'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
})
export async function POST(req: NextRequest) {
const sig = req.headers.get('stripe-signature')!
const buf = await req.arrayBuffer()
const event = stripe.webhooks.constructEvent(
buf,
sig,
process.env.STRIPE_WEBHOOK_SECRET!,
)
if (event.type === 'checkout.session.completed') {
// TODO: mark order as paid
}
return NextResponse.json({ received: true })
}

To test this webhook locally

Start a listener in another terminal:

stripe listen --forward-to localhost:3000/api/webhook

The command prints whsec_…—paste that into STRIPE_WEBHOOK_SECRET.

Webhooks let you trust Stripe, not the browser, for payment state.

7. Create a product & price

  1. In your Stripe Dashboard → Products → + Add product.
  2. Copy the Price ID (price_123). This is what you pass to the API route.

Nothing to migrate or seed; Stripe is your product catalog.

8. Run locally

pnpm dev # http://localhost:3000

Click Buy → pay with the details from the Stripe test card details

see the success page.

9. Deploy

The repo is Vercel-ready:

vercel --prod

Add the three Stripe env vars in the Vercel dashboard.

We're done.

Common mistakes

  • StripeSignatureVerificationError: Check your STRIPE_WEBHOOK_SECRET matches the CLI output.
  • undefined NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Check if the env var is missing in deployment.
  • 404 on /api/checkout: Check if the file path is /app/api/checkout/route.ts (note the route.ts).

Want to ship better features with AI?
Join my free weekly newsletter.

No spam guaranteed Unsubscribe whenever