Skip to Content
StackBlaze Templates — operator documentation
AI SaaS KitDeploymentStripe in production

Stripe in production

Two one-time setups take billing from “works locally” to “works for real customers”: a production webhook, and the customer portal’s plan-switching feature.

1. Register the production webhook

In local dev you forward events with stripe listen. In production, Stripe delivers them directly.

Create the endpoint

In the Stripe Dashboard → Developers → Webhooks → Add endpoint, point it at:

https://your-domain.com/api/billing/webhook

Subscribe at least to customer.subscription.created, .updated, and .deleted.

Set the signing secret

Copy the endpoint’s signing secret (whsec_…) into the STRIPE_WEBHOOK_SECRET env var on Vercel, and redeploy.

Use live-mode keys and price ids in production, and make sure the webhook endpoint belongs to the same Stripe account as your STRIPE_SECRET_KEY. Mismatched accounts are the #1 cause of “payment succeeded but plan didn’t update”.

2. Enable plan switching in the customer portal

Existing subscribers change plans in place through Stripe’s hosted portal. That requires the portal’s subscription-update feature to be on, with your paid products listed. Otherwise switching fails with:

This subscription cannot be updated because the subscription update feature in the portal configuration is disabled.

(The app catches this and returns a clear message instead of a 500 — but you still need to enable it.)

Dashboard: Settings → Billing → Customer portal → Subscriptions → turn on Customers can switch plans → add your Pro and Team products → Save.

Or via API (fill in your config id and product ids):

set -a; . ./.env.local; set +a curl -s -X POST "https://api.stripe.com/v1/billing_portal/configurations/<bpc_default_id>" \ -u "$STRIPE_SECRET_KEY:" \ -d "features[subscription_update][enabled]=true" \ -d "features[subscription_update][proration_behavior]=create_prorations" \ -d "features[subscription_update][default_allowed_updates][]=price" \ -d "features[subscription_update][products][0][product]=<prod_pro>" \ -d "features[subscription_update][products][0][prices][]=$STRIPE_PRICE_PRO" \ -d "features[subscription_update][products][1][product]=<prod_team>" \ -d "features[subscription_update][products][1][prices][]=$STRIPE_PRICE_TEAM"

Find <bpc_default_id>:

curl -s https://api.stripe.com/v1/billing_portal/configurations \ -u "$STRIPE_SECRET_KEY:"

New subscriptions (free → paid via Checkout) work without this. It’s only required for existing subscribers switching between plans.