# Before Bolt subscriptions go live, check the Stripe production cutover

> A Bolt-exported app can pass preview and still break on live Stripe payments. Check test vs live config, the production webhook, and access state before real customers pay.
> By Dave · 2026-11-02
> Source: https://otf-kit.dev/blog/bolt-exported-app-stripe-live-cutover

After export, a successful preview can still hide a broken live payment flow. Treat production as a separate Stripe cutover, not as the last click in a deploy.

When an app builder reports that Stripe webhooks stopped working after export, it is tempting to blame the payment provider or rewrite the checkout page. First separate the path into three things that can fail independently: which environment the app is using, where the server creates a checkout session, and which webhook endpoint receives and verifies events.

One builder described exactly this class of breakage after exporting an app: webhook failures and subscriptions that did not behave as expected. That is one report, not evidence that every export has the same cause. The useful response is a checklist that makes the cutover observable before real customers depend on it.

## Start with the payment path, not the button

Draw the flow from the user’s click to the state your app stores:

1. The browser requests a checkout session from your server.
2. The server chooses a product or price and asks Stripe to create a session.
3. Stripe returns a hosted checkout URL, and the browser redirects.
4. Stripe sends events to a registered webhook endpoint.
5. Your server verifies the event and updates the account or subscription state.
6. The product reads that state and grants the intended access.

If the checkout screen opens but the user’s account never becomes active, steps four through six deserve attention. If the server cannot create a session, start at the environment and server request. A button that navigates successfully proves neither event delivery nor entitlement updates.

Write down the owner of each step: browser, app server, Stripe account, webhook receiver, and database. After an export, confirm every URL and secret belongs to the deployed app—not only to the original preview environment.

## Separate test and live configuration

Stripe’s test and live modes use different keys and separate resources. A product or price configured in one mode is not automatically available in the other. Make an explicit configuration checklist for the deployed environment:

- The public key in the browser is the intended mode’s publishable key.
- The server’s secret or restricted key is stored only in server-side configuration.
- The price identifier sent to the server exists in the same mode as that key.
- The success and cancel destinations return to the deployed app’s expected routes.
- The deployed process has restarted or otherwise loaded the intended configuration.

Never paste a secret key into a React component, a public repository, a browser console, or a support message. If a secret was exposed, rotate it using Stripe’s current key-management process and update the server environment. Do not “fix” a production mismatch by moving a live secret into client code.

Use separate names for preview and production variables so the difference is visible during review. For example, a deployment checklist can say “preview key + preview price” and “live key + live price” without copying any secret values into the document. Store the actual credentials in the hosting platform’s secret manager.

![Illustrative test and live checklist for a Bolt-exported app: mode-specific API keys, a matching webhook endpoint, and its signing secret, with no secret values shown.](https://cdn.otf-kit.dev/blog/bolt-exported-app-stripe-live-cutover/inbody1-20261031-cd0cf707-aria-v1-r2.png?v=20261102-cd0cf707)

*Illustrative checklist: keep each mode's keys, endpoint, and signing secret paired. No values are shown.*

## Register the production webhook destination

A local or preview webhook listener is not automatically the endpoint Stripe should call for a live app. Register the deployed HTTPS endpoint in the Stripe environment that will process real payments, then copy that endpoint’s signing secret into the server-side secret store for that deployment.

Keep a short record of the endpoint URL, selected event types, environment, and the secret’s storage name. Do not record the secret itself. Preview and production endpoints can have different signing secrets; using the preview secret in production makes signature verification fail even when Stripe reaches the server.

The receiver should verify Stripe’s signature against the unmodified request body using the signing secret for that endpoint. If you parse or rewrite the body before verification, the signature check can fail. The handler should return a successful response after it has accepted the event for processing. If the work is asynchronous, persist or enqueue the event before acknowledging it, so a process restart does not silently discard the update.

![Example webhook sequence: receive the event, verify its signature, persist the subscription change on the server, then check access from saved state.](https://cdn.otf-kit.dev/blog/bolt-exported-app-stripe-live-cutover/inbody2-20261031-69d16115-aria-v1-r2.png?v=20261102-69d16115)

*Example sequence only. It is a generic pattern, not a shipped SaaS Dashboard Kit feature.*

## Test one complete event path

Before opening a real checkout to customers, run one controlled test through the deployed application and inspect both sides of the path:

1. Create a checkout from the deployed app using the intended test environment.
2. Complete the test payment with Stripe’s documented test method.
3. Confirm the registered endpoint shows a successful delivery attempt.
4. Inspect your server logs for signature verification and handler outcome, without logging secret values or payment details.
5. Read the resulting account or subscription state from the database.
6. Sign in as that test user and confirm the product’s access matches the saved state.
7. Trigger a cancellation or renewal-related test if your application’s access logic depends on it.

Do not treat a redirect to a “success” page as proof of payment. A success URL is part of the browser flow; the webhook is a separate server-to-server event. Your application should decide what access to grant from trusted server-side payment state, not from a query parameter that a browser can change.

Also test the failure path. Send an event that your handler does not recognize and confirm it is safely recorded or ignored. Retry a delivery and confirm the same event does not create duplicate entitlements or duplicate business actions. Stripe can deliver events asynchronously, and solid handlers should be designed for retries rather than assuming each event arrives exactly once. For a deeper implementation note on that pattern, see [storing idempotency keys in Postgres](/blog/idempotency-keys-owned-api).

## Debug in the order the evidence points

When an event is missing, use the event delivery record to answer a few concrete questions:

- Was the event created in test or live mode?
- Was it sent to the expected endpoint URL?
- Did the endpoint return a non-success status or time out?
- Did signature verification fail?
- Did the handler accept the event but fail while writing application state?
- Did the database update succeed while the UI continued to show stale state?

Each answer points to a different owner. A wrong mode suggests configuration. No delivery attempt suggests endpoint registration or event selection. A failed HTTP response points to reachability or handler behavior. A signature error suggests the wrong endpoint secret, an altered request body, or verification code. A successful delivery with no access change points to application state transitions.

Change one variable at a time and keep the failing event ID, timestamp, mode, endpoint, response code, and sanitized log excerpt together. That makes it possible to compare the preview and production paths without exposing credentials. Avoid rotating every key or changing product logic before you know which boundary failed.

## Use a cutover checklist that can be repeated

Before the first real subscription, have a second person walk through this list against the deployed environment:

- The live deployment uses the intended Stripe account and live-mode resources.
- Checkout sessions are created on the server; secret credentials never reach the browser.
- The configured live price exists in the selected mode.
- The production webhook uses the deployed HTTPS URL and its own signing secret.
- The receiver verifies signatures and safely handles retries.
- A test event reaches the endpoint and produces the expected database state.
- The application grants and removes access from verified server-side state.
- Logs show enough to diagnose failures and contain no secrets or unnecessary payment data.
- A rollback or disable switch is known in case a real event behaves unexpectedly.

If any item is unknown, keep the payment path in test mode and resolve that gap first. The goal is not to add every billing feature before launch; it is to know which environment is active and prove the small path you intend to rely on.

## Where a starter helps—and where it stops

An editable SaaS starter can save time on the dashboard, sign-in, data model, and application shell. It cannot make your Stripe account, production endpoint, prices, webhook secrets, or subscription access rules correct by itself. Treat those as an integration you own and verify against your own deployment.

The OTF SaaS Dashboard Kit provides editable dashboard and API scaffolding with its own Postgres database. It does not include subscription billing for your product. Use its live demo and product page to inspect the shell, then plan the payment integration as a separate work item. The decision here is whether you want a faster starting point for the app around billing—not a claim that the billing path is already implemented.

## Sources

- [Stripe API keys and environment modes](https://docs.stripe.com/keys) — key types, test and live modes, and the distinction between API keys and webhook signing secrets.