Incident pattern

Stripe test keys shipped to production

Checkout appears to work — sometimes a test card even succeeds — but no real payouts arrive, real cards get declined, or the Stripe dashboard shows zero live activity while your app claims sales.

  • Bubble
  • Lovable
  • Bolt
  • Checkout & payments
  • Webhooks

Root cause, in plain English

Stripe runs two parallel environments: pk_test/sk_test keys talk to test mode, pk_live/sk_live to live mode, and objects don't cross over. AI builders and templates scaffold with test keys, so publishing without swapping every key means production happily transacts in a sandbox: test cards succeed, real cards are refused, no money moves, and live webhooks never fire because the events exist only in test mode. Mixed modes fail at confirmation instead.

How to fix it

  1. Audit every place a Stripe key lives: frontend environment variables, backend/edge-function secrets, the webhook signing secret, and any price or product IDs (test and live catalogs have different IDs).

  2. View your production page source and search for pk_test — if it's in the served bundle, the frontend is in test mode regardless of what the backend holds.

  3. Swap to a matching live set: pk_live with sk_live, live price IDs, and a live-mode webhook endpoint with its own whsec_ secret. Half-swapped is still broken.

  4. Make one real purchase with a real card for a small amount, verify the payment in the live dashboard and the fulfillment in your app, then refund it.

  5. Keep test keys in development environments only, and name the variables identically across environments so a deploy can't pick up the wrong one by path.

How Nightlamp detects this automatically

  • Keyword check
  • Browser journey
  • API canary

An http_keyword check scans your production checkout's served code for pk_test and alerts the moment a deploy ships test keys. A browser_journey runs the live checkout to the payment form and catches mode-mismatch failures at confirm time, and an api_canary verifies your payment endpoints respond like live mode.

Catch this before your customers do

Nightlamp runs these checks continuously against your live app and sends a plain-English diagnosis — not a wall of logs — the moment this pattern shows up.

Frequently asked questions

How do I tell which mode my production app is really in?
Search the served page source for pk_test vs pk_live, then check which dashboard shows your traffic: events in Stripe's test-mode view while live mode is silent means your keys are test keys, whatever your env files claim.
Customers say they paid, but live mode shows nothing. Did they?
If the app ran on test keys, no real charges were made — "successful" payments used test cards or never moved money. Check the test-mode dashboard for those sessions, contact affected customers, and re-collect payment honestly after fixing the keys.
Why did checkout break only at the final confirm step?
That's the signature of mixed modes: a live publishable key renders the payment form, but a test secret key (or test price ID) can't complete the intent, so everything looks fine until confirmation fails. All four pieces — both keys, price IDs, webhook secret — must come from the same mode.

Newsletter

Get new incident patterns as we publish them

One email when new failure patterns, fixes, and monitoring recipes for no-code and AI-built apps land. No fluff, unsubscribe any time.

Double opt-in. One-click unsubscribe. No spam, ever.