Bolt Stripe payments: setting up checkout that works in production
Bolt has a built-in Stripe integration. Add login and a Bolt or Supabase database first, then click your project name, Integrations, Stripe, paste a test secret key and pick your products. Bolt creates the server functions for checkout and webhooks. Test on the published site with Stripe test cards before you swap in a live key.
Bolt has a Stripe integration built into the product, which changes where payments go wrong. The risk sits less in the Stripe code itself and more in the order you set things up, custom Stripe code prompted in alongside the integration, and checkout that was only ever tested in the preview.
Symptom
What you might see:
- Bolt stops partway through and asks for a key, or a server function fails because a key is missing.
- The checkout button does nothing, or behaves oddly, inside the Bolt preview.
- Payment succeeds in Stripe, but the user's account never upgrades.
- A webhook you set up yourself fails with an authorization error.
- An older project calls
stripe.redirectToCheckoutand checkout no longer opens.
When a function needs a key you haven't added, Bolt shows this message:
Why it happens
The integration has prerequisites. Bolt's Stripe integration needs a Bolt database or Supabase, and user authentication already set up. It runs on Supabase edge functions (Bolt calls them server functions) and doesn't work with Firebase. Ask for payments before login exists and you get either a stalled setup or a custom build.
Custom Stripe code is a separate thing. If you prompted Bolt to "add Stripe" before using the integration, you have a custom implementation. Bolt's docs say these can't migrate to the built-in integration. They keep working, but you own the webhook handling, and that is where they break.
Server functions expect a logged-in caller. Bolt's troubleshooting docs explain that server functions expect authenticated requests by default. Stripe's webhook calls come from outside your app with no JSON Web Token, so a custom webhook function with JWT verification on rejects every event.
Keys only work as secrets. Bolt stores your Stripe key as a secret, and secrets only reach server functions. That is correct, but a missing or misnamed secret stops checkout. The name on the Secrets page must match exactly the name the function reads.
The preview is a frame. Bolt already tells you to test Google sign-in on the published site because the preview runs in an iframe. Stripe's docs advise against placing Checkout inside another iframe, because some payment methods redirect to confirm payment.
Stripe.js changed. Stripe removed stripe.redirectToCheckout. Code generated against the old pattern needs a plain redirect to the Checkout Session URL.
The fix
1. Set up in the right order
- If the app has no login yet, prompt Bolt to add it and confirm sign-up works.
- In Stripe, switch to a sandbox (test mode), open the Developers section and create a secret key. Test keys start with
sk_test_. - In Bolt, click your project name at the top left, hover over Integrations, then click Stripe.
- Click Add Stripe API Key, paste the test key, then Retrieve my products.
- Select products, click Apply selection, then Apply changes when Bolt asks.
Bolt then creates the functions for checkout sessions, webhook processing and subscription management, and registers the webhook endpoint and signature check for you. You don't configure the endpoint in the Stripe dashboard.
2. Test on the published site
Publish, open the live URL in a normal tab and run these Stripe test cards from Bolt's docs:
| Card | Result |
|---|---|
4242424242424242 | Successful payment |
4000000000000002 | Declined |
4000002500003155 | Needs authentication |
Then try the case that catches most apps: pay, and close the tab before the success page loads. If the account still upgrades, your app is listening to Stripe's webhook rather than the page. If it doesn't, fix that before launch.
3. Redirect to Checkout the current way
If your project still calls stripe.redirectToCheckout, replace it. Stripe's changelog recommends a standard redirect to the session URL:
// Call the server function that creates the Checkout Session.
// Use the function name from database icon > Server Functions.
const { data, error } = await supabase.functions.invoke("create-checkout", {
body: { priceId },
});
if (error) throw error;
window.location.href = data.url;4. Fix a custom webhook function
This only applies if you built Stripe by prompting rather than with the integration. Ask Bolt to turn off JWT verification for the webhook function only, then make sure the function verifies Stripe's signature against the raw body:
// Supabase edge function (Deno). Match the Stripe version your other functions use.
import Stripe from "npm:stripe@^23";
import { createClient } from "npm:@supabase/supabase-js@2";
const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")!); // use your secret names
const cryptoProvider = Stripe.createSubtleCryptoProvider();
// SUPABASE_SERVICE_ROLE_KEY is Supabase's legacy key name; projects on the
// new API keys get secret keys in SUPABASE_SECRET_KEYS instead.
const db = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);
Deno.serve(async (req) => {
const signature = req.headers.get("Stripe-Signature");
const body = await req.text(); // raw body, or the signature check fails
let event: Stripe.Event;
try {
event = await stripe.webhooks.constructEventAsync(
body, signature!, Deno.env.get("STRIPE_WEBHOOK_SECRET")!, undefined, cryptoProvider,
);
} catch (err) {
return new Response(`Webhook error: ${(err as Error).message}`, { status: 400 });
}
if (event.type === "checkout.session.completed") {
const session = event.data.object as Stripe.Checkout.Session;
// client_reference_id must be set to the user's id when the session is created
await db.from("profiles").update({ plan: "pro" }).eq("id", session.client_reference_id);
}
return Response.json({ received: true });
});The webhook signing secret is on the endpoint's detail view in the Webhooks tab of Stripe's Workbench. Add it on the Secrets page under the exact name the function reads. Bolt reserves secrets prefixed SUPABASE_ for its own database connection.
5. Go live deliberately
Bolt's docs cover test mode in detail and say less about the switch to live, so treat it as a checklist rather than a toggle:
- Create a live secret key and replace the value of the Stripe secret on the Secrets page.
- Check your live Stripe account has the same products and prices, and that the price IDs your app uses are the live ones.
- In the Stripe dashboard, confirm a live-mode webhook endpoint exists and points at your published app.
- Make one real low-value payment, then refund it.
How to prevent it
- Use the integration rather than prompting for Stripe from scratch, unless you need something it doesn't cover.
- Keep test and live keys apart, and rotate keys you pasted anywhere outside Secrets.
- Run Publish, then Run security audit before launch on a paid plan. It checks for misuse such as changing a price or claiming a single-use item twice.
- For an Expo mobile app, plan in-app purchases early: Bolt's Expo docs point to RevenueCat for subscriptions inside the app.
When to get help
Payments are where a small mistake costs real money. The prototype to product guide covers the wider launch, and adding Stripe to an AI-built app goes deeper on webhooks, renewals and refunds. If sign-in is shaky, fix that first with Bolt auth not working, because the integration depends on it.
For the full Bolt launch checklist, read taking a Bolt app to production. Run the free vibe-code health check for a first read, and if the payment flow needs fixing rather than finishing, a Rescue Sprint is an audit plus fixes at a fixed price agreed after a technical assessment (from £750).
- Bolt help: Stripe for payments, checked 10 November 2026
- Bolt help: Integrations issues (server functions, webhooks, missing secrets), checked 10 November 2026
- Bolt help: Secrets, checked 10 November 2026
- Bolt help: Server functions, checked 10 November 2026
- Bolt help: Check your project's security, checked 10 November 2026
- Bolt help: Database authentication settings (preview and iframes), checked 10 November 2026
- Bolt help: Expo for mobile apps, checked 10 November 2026
- Stripe changelog: Removes support for redirectToCheckout, checked 10 November 2026
- Stripe docs: Embedded Checkout quickstart, checked 10 November 2026
- Stripe docs: Testing, checked 10 November 2026
- Supabase docs: Function configuration (verify_jwt), checked 10 November 2026
The launch checklist for founders, as a PDF
About 40 checks across legal, payments, domain and email, analytics, backups and support.
Questions founders ask
Does Bolt support Stripe?
Yes. Bolt's Stripe integration supports one-off payments and subscriptions, imports your Stripe products, and creates the server-side functions for checkout and webhooks. It needs user login and a Bolt or Supabase database.
Why does my Bolt Stripe webhook return an authorization error?
Bolt's server functions expect a signed-in user's token by default, and Stripe doesn't send one. Bolt's docs say to turn off JWT verification for the webhook function and check Stripe's signature instead.
Can I move my existing Stripe code onto Bolt's integration?
Bolt's docs say existing custom Stripe implementations can't migrate to the built-in integration, though they keep working.
Can I use Stripe in a Bolt mobile app?
For paid features inside an Expo app, Bolt's Expo docs point to RevenueCat for in-app purchases. They don't cover using the Stripe integration in a mobile app.

Ant
Product designer who builds MVPs, based in Nottingham. 10+ years designing digital products, and the only person at BlackFig. More about me