FixLoop

Lovable Stripe works in test mode, fails in live

Guide · October 20, 2026

Stripe checkout works perfectly in test mode. You flip to live mode and nothing works. No errors in your code, no obvious typos, just payments that silently fail or 400 from the Stripe API. This happens constantly in Lovable + Supabase apps because test mode and live mode are completely separate worlds.

Test keys, test webhooks, test products, and test prices do not exist in live mode. Every one of them needs a live counterpart, and the live counterpart needs to be wired up in the right place.

Stuck on this right now? Paste your error and we'll email you the likely cause within 24 hours, free.

1. Test vs live API keys in the wrong place

What it looks like: Checkout opens but charges never appear in the live Dashboard. Or the API returns Invalid API Key provided or a key mismatch error.

How to check: Open your Supabase Edge Function secrets (or wherever your backend reads STRIPE_SECRET_KEY). The key should start with sk_live_ for production. If it starts with sk_test_, you are still in test mode.

Check your frontend too. The publishable key in your client code should start with pk_live_ for production. A test publishable key paired with a live secret key (or the reverse) will cause Stripe.js to fail to load the session or payment. Both keys must come from the same mode.

How to fix: Get your live keys from the Stripe Dashboard (make sure you are viewing live mode, not test mode). Replace both:

If you deploy through Cloudflare Pages or Netlify, those keys need to exist in the Production environment, not just Preview. See why preview works and deploy breaks for more on that.

2. Publishable and secret keys from different accounts

What it looks like: Stripe returns No such payment_intent, No such customer, or similar "not found" errors even though the object exists in your Dashboard.

How to check: Every Stripe key belongs to one account and one mode. If your frontend uses a publishable key from Account A and your backend uses a secret key from Account B, objects created by one cannot be read by the other.

How to fix: Pick one Stripe account. While viewing the same mode (test or live) in the Dashboard, copy both the publishable and secret keys from that account. Replace any keys from other accounts or modes.

3. Webhook endpoint and signing secret per mode

What it looks like: Checkout completes but the webhook never fires, or it fires and your Edge Function returns 400 because the signature check fails. stripe-node logs something like No signatures found matching the expected signature for payload.

How to check: In Stripe Dashboard, go to Developers, then Webhooks. You should see separate endpoints for test and live mode. Each endpoint has its own signing secret (whsec_...). Click the live endpoint and reveal the secret.

Compare that secret to whatever your production code uses as STRIPE_WEBHOOK_SECRET. If they do not match, signature verification fails.

How to fix:

  1. Create a live webhook endpoint in the Stripe Dashboard if you only have a test one. Point it at your production Edge Function URL.
  2. Copy the live endpoint signing secret.
  3. Set STRIPE_WEBHOOK_SECRET in your production secrets to that whsec_... value.

If your webhook is returning 401 instead of 400, that is a different problem: the Supabase platform is rejecting the request before your code runs. See Stripe webhook 401 on Supabase Edge Functions for that fix.

4. Products and prices only exist in test mode

What it looks like: Stripe returns No such price: 'price_...' or No such product when creating a Checkout Session in live mode.

How to check: Products and prices created in test mode do not exist in live mode. They are completely separate. If your code has a hardcoded price_1ABC... that you created while in test mode, that ID does not exist in live mode.

In the Stripe Dashboard, switch to live mode and go to Products. If your products are missing, you need to create them.

How to fix: Recreate the same products and prices in live mode in the Dashboard. Once created, update your code to use the live price IDs, or set the same lookup key on the price in both modes so your code can resolve it dynamically.

// Hardcoded price ID - fails with "No such price" when switching modes
const priceId = 'price_1ABCtestModeId';

// Better: set the same lookup key on the price in both test and live mode
const prices = await stripe.prices.list({ lookup_keys: ['pro_monthly'], limit: 1 });
const priceId = prices.data[0].id;

5. Stripe account not activated for live payments

What it looks like: Live API calls return errors about the account not being ready, or live charges are refused until Stripe finishes activating the account. The Dashboard may show a banner about completing activation.

How to check: Stripe requires identity verification and business details before you can accept live payments. Go to your Stripe Dashboard and look for any activation banner or check Settings for onboarding status.

How to fix: Complete the activation steps Stripe shows you. This usually means providing business information, bank account details, and identity verification. Until activation is complete, live mode is disabled even if your keys are correct.

6. Redirect URLs still pointing at preview

What it looks like: Checkout completes on Stripe's hosted page, then redirects to your Lovable preview domain instead of your production domain. Or the redirect goes to localhost.

How to check: Look at the success_url and cancel_url you pass when creating a Checkout Session. If they are hardcoded to https://preview-xyz.lovable.app or http://localhost:5173, production users land there after paying.

How to fix: In your Supabase Edge Function, read the production domain from an environment variable:

const siteUrl = Deno.env.get('SITE_URL') ?? 'https://yourapp.com';

const session = await stripe.checkout.sessions.create({
  // ...
  success_url: `${siteUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${siteUrl}/pricing`,
});

Set SITE_URL in your Edge Function secrets to the production domain (e.g. https://yourapp.com), not the preview URL.

Quick checklist

Questions

Why does Stripe work in test mode but fail in live mode?

Test mode and live mode use completely separate API keys, webhook secrets, products, and prices. If any of these are mismatched or missing in live mode, payments fail. The most common cause is using sk_test_ keys instead of sk_live_ keys in production.

Do I need separate webhook secrets for test and live mode?

Yes. Each webhook endpoint has its own signing secret (whsec_). If you created a test endpoint and a live endpoint, they have different secrets. Your production code must use the live endpoint secret.

Why does Stripe say my price ID does not exist?

Products and prices created in test mode do not exist in live mode. A hardcoded price_... ID from test mode fails with No such price in live mode. Recreate the products and prices in live mode and use those IDs.

How do I know if my Stripe account is activated for live payments?

In the Stripe Dashboard, check the top banner or Settings for activation status. If onboarding is incomplete, live mode is disabled even with correct keys. Stripe shows what information is missing.

Webhook returning 401 with "Missing authorization header"? That is the Supabase JWT check, not a Stripe problem: Stripe webhook 401 on Supabase Edge Functions. Frontend env vars missing in production? Why preview works and deploy breaks.

Stripe still broken in production? That is a Stripe fix. $299, you test the PR before you pay. No fix, no pay.

Get this fixed for $299

Still stuck? Paste the error and we'll email you the likely cause, free.