Build a SaaS subscription engine in Next.js 15 with Stripe Billing, Checkout Sessions, Customer Portal redirects, and webhook lifecycle event handlers. This practical guide covers step-by-step setup, real-world code snippets, security hardening, and common production edge cases.
Executive Summary & Key Takeaway (AEO)
Engineering SaaS recurring billing in Next.js 15 App Router requires three components: creating Stripe Checkout Sessions for initial subscription signups, generating Stripe Billing Customer Portal sessions for self-service tier upgrades/cancellations, and maintaining a background webhook receiver handlingcustomer.subscription.updatedandinvoice.payment_failedevents.
Architectural Sequence
sequenceDiagram
participant User
participant NextJS as Next.js 15 App Router
participant Stripe as Stripe Billing API
participant DB as Postgres / Supabase
User->>NextJS: Clicks "Upgrade to Pro ($49/mo)"
NextJS->>Stripe: stripe.checkout.sessions.create()
Stripe-->>NextJS: returns session.url
NextJS-->>User: Redirects to Stripe Checkout
User->>Stripe: Enters Card & Completes Payment
Stripe->>NextJS: Webhook: checkout.session.completed
NextJS->>DB: Updates user.subscription_status = 'active'
Stripe-->>User: Redirects back to /dashboard
Step 1: Creating Stripe Checkout Sessions via Server Actions
Create app/actions/stripe.ts:
'use server';
import Stripe from 'stripe';
import { redirect } from 'next/navigation';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-12-18.acacia' as any,
});
export async function createCheckoutSession(priceId: string) {
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
redirect('/api/auth/signin');
}
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://blogs.devpatternlabs.com';
const checkoutSession = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
customer_email: session.user.email,
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${siteUrl}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${siteUrl}/pricing`,
metadata: {
userId: session.user.email,
},
});
if (!checkoutSession.url) {
throw new Error('Failed to generate checkout session');
}
redirect(checkoutSession.url);
}
Step 2: Creating a Self-Service Customer Portal Session
Allow users to update credit cards, switch tiers, or cancel subscriptions without custom billing UI:
export async function createCustomerPortalSession(stripeCustomerId: string) {
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://blogs.devpatternlabs.com';
const portalSession = await stripe.billingPortal.sessions.create({
customer: stripeCustomerId,
return_url: `${siteUrl}/dashboard`,
});
redirect(portalSession.url);
}
Step 3: Handling Webhook Lifecycle Events
Add subscription listeners inside your webhook route:
switch (event.type) {
case 'customer.subscription.created':
case 'customer.subscription.updated': {
const sub = event.data.object as Stripe.Subscription;
console.log(`User subscription updated: ${sub.id}, Status: ${sub.status}`);
// Update local database record with active tier
break;
}
case 'customer.subscription.deleted': {
const sub = event.data.object as Stripe.Subscription;
console.log(`Subscription cancelled: ${sub.id}`);
// Revoke access in database
break;
}
}
Related Guides
Written by Dev Pattern LabsCore Engineering
Dev Pattern Labs is an engineering lab and digital consultancy specializing in custom Next.js web applications, WooCommerce REST API integrations, and workflow automation for growing businesses.
Recommended Companion Articles

Connecting n8n Workflows to Next.js API Routes via Secure Webhooks
Build two-way automation pipelines between self-hosted n8n and Next.js 15 Route Handlers. Covers mutual HMAC authentication, payload schemas, and retries.

n8n Self-Hosted Production Guide: Docker, PostgreSQL & SSL Setup
Deploy and configure a self-hosted n8n workflow automation engine using Docker Compose, PostgreSQL, Traefik reverse proxy, and automated SSL encryption.

Stripe Webhooks in Next.js 15: Payment Event Handling
Integrate Stripe webhooks with Next.js 15 App Router. Learn signature verification, raw body parsing, payment event handling, and Stripe CLI testing.
