Stripe Integration Guide: Subscriptions, Webhooks, and the Customer Billing Portal
Everything you need to implement Stripe subscriptions in a Next.js SaaS product — from initial checkout to webhook handling, dunning management, and the self-serve billing portal.
By POINTNEXIS Team

Stripe is the standard payment infrastructure for SaaS products, but a complete integration is more involved than most developers expect. Getting checkout working is the easy part — handling subscription lifecycle events, failed payments, and customer self-service is where most implementations fall short.
This guide covers the full stack: Checkout, webhooks, the Customer Portal, and the patterns that handle edge cases gracefully.
Stripe Checkout and Customer Creation
Create a Stripe Customer on your backend when a user signs up — do not wait until checkout. Store the `stripe_customer_id` in your database mapped to the user record. This lets you pre-fill checkout, retrieve payment history, and manage subscriptions without forcing a re-authentication flow.
Use Stripe Checkout (hosted payment page) over building a custom card form unless you have specific UX requirements. Checkout handles PCI compliance, 3DS authentication, global payment methods, and coupon redemption out of the box.
Webhook Handling: The Source of Truth
Stripe webhooks are how your database stays in sync with subscription state. Never trust the redirect URL after checkout as confirmation — it can be spoofed or the user may close the tab. Process `checkout.session.completed`, `customer.subscription.updated`, `customer.subscription.deleted`, and `invoice.payment_failed` events.
Verify every webhook with the Stripe signature in the `stripe-signature` header before processing. Make webhook handlers idempotent — Stripe may deliver the same event multiple times. Check if you have already processed an event ID before modifying your database.
The Customer Billing Portal
Stripe's Customer Portal handles subscription upgrades, downgrades, cancellations, and payment method updates without you building a single UI screen. Create a portal session server-side and redirect the user: `stripe.billingPortal.sessions.create({ customer: customer_id, return_url: appUrl })`.
Configure the portal in the Stripe Dashboard: allowed cancellation options, plan change permissions, and whether to prorate plan changes. Prorated credits and mid-cycle upgrades are handled automatically.
Dunning: Recovering Failed Payments
Stripe's Smart Retries automatically retry failed payments based on historical success patterns across their network. Enable dunning emails in the Stripe Dashboard — they handle the customer communication for you.
When a subscription moves to `past_due`, gate access in your application. When it reaches `canceled` after the grace period, downgrade the account. POINTNEXIS SaaS builds wire up this state machine during the initial build — retrofitting payment failure handling is the most common expensive fix teams wish they had done from the start.