Stripe Billing for SaaS in 2026: Subscription Patterns That Actually Ship
StripeBackendNext.js

Stripe Billing for SaaS in 2026: Subscription Patterns That Actually Ship

Real patterns I use to wire Stripe Billing into multi-tenant SaaS — checkout, webhooks, customer portal, plan changes, dunning, and the gotchas that break apps in production.

HJ
Hassan Javed
May 2026
11 min read

Why Stripe still wins for SaaS

In 2026 there are real alternatives — Paddle, Polar, Lemon Squeezy — and each has a niche. But for most B2B SaaS, Stripe Billing is still the default. The reasons are boring: mature API, every framework has a working integration, customer portal saves you a month of work, and your future enterprise customers expect Stripe-quality reliability.

After wiring Stripe into a dozen SaaS apps over the last two years, here is the playbook I now reach for.

The mental model

A SaaS subscription in Stripe is three connected objects:

Customer — the billing entity (often equals your tenant or org)
Subscription — links a customer to one or more prices, runs the billing cycle
Price — the actual amount for a product, plus interval (monthly, yearly, usage-based)

Your DB does not need to mirror all of Stripe. You only need to store the bare minimum: a stripeCustomerId on your tenant, and plan (a string like "free", "pro", "team") on the tenant. The rest lives in Stripe and you read it via the API or webhooks.

Setup that I do once and forget

In the Stripe dashboard, before writing any code:

1.Create three products: Free, Pro, Team (or whatever your tiers are)
2.For each product, create two prices: monthly and yearly
3.Enable the Customer Portal in test mode
4.Add your test webhook endpoint and copy the signing secret
5.Set business info (legal name, address, tax id) — required before live mode

Skip step 5 and you will discover you cannot accept real money the day you launch. I have done it.

Checkout flow

Two options:

Stripe Checkout (hosted) — Stripe-hosted page. Zero UI work. Handles tax, coupons, 3DS, mobile. Use this unless you have a reason not to.

Embedded Checkout or Payment Element — your domain, your design. Use when you have brand requirements or want a true single-page app feel.

For 95 percent of SaaS, hosted Checkout is the right call. The UX is already better than what most teams will build in-house.

The flow:

1.User clicks "Upgrade to Pro" in your app
2.Your server creates a Checkout Session and redirects them
3.They pay on the Stripe-hosted page
4.Stripe redirects back to your success URL
5.Your webhook handler updates your DB — do not trust the success URL alone

The webhook section

This is where every SaaS gets bitten the first time. Webhooks are at-least-once delivery, which means you will receive the same event twice eventually. Your handlers must be idempotent.

Pattern I use:

1.Verify the webhook signature with Stripe's library — never skip this. Without it, anyone can forge events to your endpoint.
2.Look up the event ID in your DB. If you have processed it, return 200 and exit.
3.Otherwise, process it inside a DB transaction that also inserts the event ID.
4.If anything throws, the transaction rolls back, and Stripe retries.

The minimum events you must handle:

checkout.session.completed — user finished checkout, create or upgrade subscription
customer.subscription.updated — plan change, quantity change, anything
customer.subscription.deleted — cancellation took effect
invoice.payment_failed — card declined, downgrade or warn

Optional but useful:

customer.subscription.trial_will_end — three days before trial ends
invoice.payment_succeeded — for sending receipts, accounting hooks

Customer Portal

Stripe-hosted page where customers manage their own billing — change plans, update payment method, cancel, download invoices. Drop-in, branded with your logo, returns to your app when done.

This is the single biggest support burden reducer I have seen in SaaS. Customers self-serve 90 percent of billing requests. Set it up on day one.

To send a customer to the portal:

1.Create a portal session on your server with the customer's stripeCustomerId
2.Redirect them to the returned URL
3.They do whatever they need
4.They click "return to app" and land back on your dashboard

That is the whole integration.

Plan changes and proration

When a customer upgrades mid-cycle, Stripe handles proration automatically — they pay the difference. When they downgrade, you can choose: prorate immediately, or schedule the downgrade for the next renewal.

I default to scheduled downgrades. It is the right user behavior — they paid for Pro this month, let them have Pro this month. Immediate downgrades feel punitive.

Usage-based billing

If your SaaS charges by API calls, seats, or any metered usage:

Create a price with usage type metered in Stripe
Report usage via the Usage Records API throughout the billing period
Stripe sums it up at invoice time

Reporting pattern I use: batch usage events in a Redis sorted set, flush to Stripe every 5 minutes. Direct write per API call works at low volume but quickly hits rate limits.

Trials

Two approaches:

1.Stripe-managed trial — set trial_period_days on the subscription. Stripe handles trial end automatically.
2.App-managed trial — your DB stores a trialEndsAt timestamp on the tenant, and you gate features in code.

I prefer app-managed because it is easier to extend trials manually for specific customers without dealing with Stripe-side date math.

Failed payments and dunning

When a card fails, Stripe automatically retries based on your dunning settings (configurable in dashboard). After all retries fail, the subscription goes to past_due or canceled.

My recommended dunning config:

3 retries over 21 days (default Stripe settings work fine here)
Email customer at each retry (Stripe sends these automatically)
Move user to read-only mode on day 7
Full cancellation on day 22

The 7-day read-only window is critical. Most failed payments are expired cards, and the user fixes it in their email when they cannot access their data. If you cut off too fast, they churn for a reason that is not really churn.

Test mode discipline

Test mode and live mode have separate API keys, separate customers, separate webhooks. Use Stripe's test card numbers extensively — they have specific cards for every failure mode you need to test.

Before going live, walk through every flow in test mode:

New customer signs up, pays, gets correct plan
Existing customer upgrades, gets prorated correctly
Card declines, dunning fires, eventual cancellation
Customer cancels via portal, retains access until period end
Refund issued, your DB reflects it via webhook

If you cannot do all six cleanly in test mode, do not flip to live.

My SaaS Stripe stack in 2026

Next.js 14 App Router with Server Actions
Hosted Checkout (not embedded)
Customer Portal
App-managed trials
Webhook handler with idempotency via event ID dedupe table
Stripe Tax enabled where required
Resend for transactional billing-adjacent emails (welcome, plan change confirmations)

Cost: Stripe is 2.9 percent plus 30 cents per transaction in the US. For SaaS at any meaningful scale, this is fine. Volume discounts exist if you grow past a few million per year.

TL;DR

Stripe Billing is still the default for B2B SaaS in 2026
Hosted Checkout, Customer Portal — use both, skip the custom UI
Webhook handlers must be idempotent (dedupe by event ID)
Handle 4 events minimum: checkout completed, subscription updated, subscription deleted, payment failed
Schedule downgrades, do not immediate-prorate
Test mode discipline before going live

If you are wiring Stripe Billing into a SaaS and want a senior to architect it correctly the first time, contact me.

Related Reads

You might also like