Backstop App broke? Get it fixed →
Lovable · Stripe · Checkout

Lovable checkout not working? 7 causes and how to fix each.

Customers can't pay, or they pay and the order never shows up. Here's a calm, ordered checklist to find which of the seven usual suspects is breaking your Lovable checkout — and the exact fix for each.

Updated June 2026 · ~7 min read

TL;DR

If payment succeeds but no order appears, it's a Stripe webhook problem (#3). If the button does nothing or you see a 500, it's a missing secret key (#1), a Supabase RLS block (#4), or CORS (#5). If it works in preview but not live, your production env vars are wrong (#2).

First, narrow it down

Before changing anything, figure out which half of the flow is broken. Open your browser's developer console (right-click → Inspect → Console), then try to check out and watch:

1. The Stripe secret key is missing or wrong

Lovable apps create the Checkout Session in a backend function (usually a Supabase Edge Function) using your STRIPE_SECRET_KEY. If that variable is unset, blank, or still a test key on a live site, the function throws before it can return a redirect URL — so the button appears dead.

Fix: In Supabase → Project Settings → Edge Functions → Secrets (or your deployment's env settings), confirm STRIPE_SECRET_KEY exists and starts with sk_live_ for production (or sk_test_ while testing). Re-deploy the function after changing a secret — secrets are baked in at deploy time.

Never paste a live secret key into client-side code or a chat prompt. A key beginning sk_live_ belongs only in server-side secrets. If one has leaked, roll it in the Stripe dashboard immediately.

2. It works in preview but not on the live site

This is the single most common "it broke in production" report. Lovable's preview environment and your deployed custom domain do not necessarily share the same environment variables. Test keys in preview, nothing (or test keys) in production.

Fix: Set the live Stripe keys on the production deployment specifically — both the secret key (server) and the pk_live_ publishable key (client). Then hard-refresh and test with a real card in a private window. If you switched test→live, remember your live mode has its own products, prices, and webhook endpoints — they don't carry over.

3. The webhook never fires (payment works, order doesn't)

When a payment succeeds, Stripe doesn't tell your app directly — it sends a webhook event (checkout.session.completed) to an endpoint you registered. Your app listens for that event and then creates the order, grants access, or starts the subscription. If the webhook is missing or rejected, the money is taken but nothing happens on your side.

Fix — check three things in order:

  1. The endpoint exists and points at production. Stripe Dashboard → Developers → Webhooks. The URL should be your live function, e.g. https://<project>.supabase.co/functions/v1/stripe-webhook — not a preview URL.
  2. The signing secret matches. Each endpoint has its own whsec_… secret. Copy it into your function's STRIPE_WEBHOOK_SECRET env var. A mismatch makes every event fail signature verification and return 400.
  3. Events are actually arriving. In the same Stripe webhooks screen, open the endpoint and look at recent deliveries. Red/failed deliveries show the response — a 400 means signature/secret, a 500 means your code threw, a timeout means the function is too slow or asleep.
Quick test: Stripe's webhook page has a "Send test event" button. Fire a checkout.session.completed and watch your function logs. If you see the event but no order is created, the bug is in your handler, not the wiring.

Don't have hours to debug this while customers can't pay?

Send me what's happening and I'll fix your checkout — flat fee, quoted up front, reply the same day. Most checkout rescues are done within a day or two.

Get my checkout fixed → Flat fee · reply today · no retainer required

4. Supabase Row Level Security is blocking the write

If your function creates the Checkout Session fine but recording the order fails, RLS is a prime suspect. Row Level Security blocks inserts that don't satisfy a policy — and a webhook runs with no logged-in user, so any policy keyed to auth.uid() will reject it.

Fix: Have the webhook/server function use the Supabase service role key (which bypasses RLS) rather than the anon key — and keep that key server-side only. Alternatively, add an explicit policy that permits the insert the webhook needs. Check Supabase → Logs → Edge Functions for a new row violates row-level security policy error to confirm.

5. A CORS error kills the request

If the console shows blocked by CORS policy, the browser refused your function's response. Edge Functions must return the right Access-Control-Allow-Origin headers and answer the preflight OPTIONS request.

Fix: Ensure the function responds to OPTIONS with the CORS headers and includes them on the real response too:

const cors = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, content-type",
};
if (req.method === "OPTIONS") return new Response("ok", { headers: cors });
// ...and spread `...cors` into the headers of your real response.

6. Wrong price ID, or test/live product mismatch

Checkout Sessions reference a Stripe price_… ID. Test mode and live mode have entirely separate products and prices. If you copied a price_ ID from test mode into a live deployment (or vice-versa), Stripe returns No such price and the session won't create.

Fix: Recreate the product/price in the correct mode and use that mode's price ID. Confirm the toggle in the top-left of the Stripe dashboard matches the keys your app is using.

7. The function went to sleep or timed out

Serverless functions cold-start. A slow first request, or a handler doing too much synchronous work, can time out before Stripe gets its 200 — so Stripe retries, sometimes creating duplicate orders, sometimes none.

Fix: Make the webhook handler return 200 as fast as possible and do heavy work after, or asynchronously. Make order creation idempotent (keyed on the Stripe session/event ID) so a retry can't double-charge or double-fulfil.

How to stop it breaking again

Checkout breaks are uniquely painful because they're silent and they cost money for every hour they're down. Two habits prevent most of it:

That's exactly what Backstop does — it runs your signup, login and checkout as real browser tests around the clock and watches your Stripe webhook, then alerts you in plain English the moment one breaks.

Catch the next break before your customers do.

Join the waitlist for Backstop — monitoring + offsite backup built for Lovable apps. We'll email you at launch, nothing else.

Frequently asked

Payment succeeded but the order never appears — why?

Almost always a Stripe webhook problem. The payment completes at Stripe, but your app never receives (or never verifies) the checkout.session.completed webhook that creates the order. Check the endpoint URL, the signing secret, and the recent deliveries log (#3 above).

It works in Lovable preview but not on my live site.

Your production deployment is missing the live Stripe keys, or still using test keys. Preview and your deployed domain can have different environment variables — set the live secret + publishable keys on production specifically (#2).

The Stripe button does nothing when I click it.

The function that creates the Checkout Session is erroring before it can redirect — usually a missing secret key (#1), a Supabase RLS block (#4), or a CORS error (#5). Open the browser console and your Supabase function logs for the exact message.