Skip to content
← Back to the blog
integrationsproduct

How to set up the Stripe customer portal for your SaaS

Let customers cancel, change plan, update their card and download invoices with the Stripe customer portal: settings to choose, the API call and webhooks.

By Devonauts Team5 min read
Leer en español →
A woman holds a bank card and a smartphone while sitting in a garden chair

The Stripe customer portal is a page Stripe hosts where your subscribers can cancel, switch plans, update their card and download past invoices, so you don't have to build any of those screens. You turn on the options you want in the portal settings of the Dashboard, add a "Manage billing" button that asks your server to create a portal session for the signed-in user, and listen to webhooks so your app notices what customers changed.

This guide covers the two ways to open the portal, which settings matter, the server code, the webhooks to handle and the mistakes that let one customer see another's billing.

Stripe offers two entry points.

No-code login linkAPI portal session
How it worksCustomer enters their email, Stripe emails them a login linkYour server creates a session for a known customer and redirects
Code neededNoneOne server endpoint
Customer can change emailNoYes, if enabled
Best forSites without login, quick startsApps where users already sign in

The login link matches customers by email. If several Stripe customers share an address, Stripe picks the most recently created one that has an active subscription, which can surprise people with duplicate accounts. If your app has its own login, the API route is more predictable because you choose exactly which customer opens.

Which portal settings should you turn on?

Open the customer portal page under the billing settings in the Dashboard. The defaults are a reasonable start, but go through these groups.

Subscriptions

  • Switch plan (off by default). Turn it on and choose which products and prices are offered. Only list plans your app knows how to handle.
  • Update quantities (off by default). Useful for per-seat pricing.
  • Prorations. Decide whether plan changes are credited immediately or at the end of the period.
  • Manage downgrades. You can schedule downgrades for the end of the billing period. Stripe does this by attaching a subscription schedule, and it only works between prices of the same product.

Cancellations

  • Cancel subscription is on by default. Choose whether it cancels immediately or at the end of the period. End of period is what most customers expect from a monthly plan.
  • Cancellation reason is on by default, and its answers are worth reading every month.
  • Retention coupons (off by default) let you offer a discount before someone confirms the cancellation.

Billing details and invoices

  • Payment methods: on, so customers can replace an expired card themselves.
  • Invoice history: on, so customers can download invoices without emailing you.
  • Billing information: name, address, phone and, optionally, tax ID.

Add your terms of service link and a default return URL, then set your logo and colours on the branding settings page. If you want the portal on your own subdomain, Stripe supports one custom domain per account.

Portal settings are stored separately for live mode and for each sandbox, so configuring one doesn't change the other. That catches many people at launch; our test-to-live checklist includes it.

How do you create a portal session from your app?

The server endpoint is short. The important part is where the customer ID comes from.

// POST /api/billing/portal  (runs on your server)
const user = await requireSignedInUser(req);          // your auth check
const customerId = user.stripe_customer_id;            // from YOUR database
const session = await stripe.billingPortal.sessions.create({
  customer: customerId,
  return_url: 'https://yourapp.com/account',
});
return redirect(session.url);

Never accept the customer ID from the browser. If your endpoint takes customer from the request body, anyone can paste someone else's ID and open their billing page, including their invoices and address. Read it from your database for the signed-in user.

The session URL is short-lived, so create it when the user clicks the button rather than putting a saved link in the page.

Can you send people straight to one action?

Yes. Portal deep links take a flow_data parameter. The flow types include payment_method_update (jump straight to "update your card"), subscription_cancel, subscription_update and subscription_update_confirm. The card update flow is especially handy from a "your payment failed" banner, which we cover in handling failed subscription payments.

Which webhooks tell you what the customer changed?

The portal changes things in Stripe, not in your database. Your webhook keeps them in sync:

  • customer.subscription.updated: plan changes, quantity changes, and cancellations scheduled for period end. For classic billing mode, check cancel_at_period_end; for flexible billing mode, check whether cancel_at is set. If the customer reactivates before the period ends, you get another update with those fields cleared.
  • customer.subscription.deleted: the subscription has ended. Remove access.
  • customer.updated: billing details or the default payment method changed.

Stripe's docs warn not to use the billing email from these events as a login credential. Treat it as billing information only. If your handler isn't set up yet, our guide on Stripe subscriptions with webhooks covers the structure.

How do you test it before customers use it?

  1. In a sandbox, create a test customer with a subscription.
  2. Open that customer in the Dashboard and use Open customer portal from the actions menu to see it as they would.
  3. Cancel, switch plans and change the card, and watch your webhook endpoint's event deliveries.
  4. Confirm your app shows "ends on [date]" for a scheduled cancellation, and removes access only when the period actually ends.

Quick answers

Does the portal cost extra? It's part of Stripe Billing. Check Stripe's current Billing pricing for your account.

Can customers resubscribe after cancelling? Before the period ends they can renew from the portal. After it ends, send them back through your normal Checkout.

Can I show different plans to different customers? Yes. Create extra portal configurations through the API and pass the configuration's ID when you create the session. The Dashboard only edits the default one.

Why does the portal show no subscriptions? Usually the session was created for a different customer ID than the one that owns the subscription, often a duplicate customer created at checkout.

Need help wiring up billing self-service?

If your portal button opens the wrong account or your app doesn't notice cancellations, our Stripe payments service covers portal sessions, webhooks and access checks. Start with the free app health check, review Stripe's customer portal integration docs, or send us a note.

Still stuck with your app?

Run the free health check on your app's URL, or get the $299 App Rescue Audit: a senior engineer reviews the code and sends you a written report in 2 business days.