Skip to content
Back to field notes
PaymentsJune 6, 20265 min read

Stripe test vs live key in production: how to confirm before launch

A deployed build still wired to Stripe test keys silently rejects every real card, so this note shows founders how to confirm live-mode keys before they announce launch.

StripePaymentsLaunch

A test key in production means nobody can actually pay

The concrete launch risk is simple and easy to miss: you announce, traffic arrives, people reach checkout, and every payment fails. Not because the code is broken, but because the deployed build is still pointing at Stripe test mode. A Stripe test key in production processes only fake card numbers. A real card hits it and Stripe rejects the charge, because test-mode and live-mode are fully separate environments with separate keys, separate data, and separate dashboards.

This is worth separating from the louder Stripe risk, which is a secret key leaking into the browser. That is a security exposure. A test key in production is mostly a revenue and trust failure: the keys may be in exactly the right place, scoped correctly, never leaked, and the app still cannot take a single real payment. The good news is that the test-vs-live distinction is unusually visible. The key prefix tells you which mode you are in.

What is observable from the outside is the publishable key prefix, because it ships in the client. Whether the server is using a matching live secret key is not visible from the public surface and has to be confirmed another way, which the fix section covers.

  • A publishable key starting with pk_test_ means the browser is talking to Stripe in test mode.
  • A publishable key starting with pk_live_ means the browser is in live mode.
  • The secret keys mirror this: sk_test_ for test, sk_live_ for live. The secret key belongs only on your server and should never appear in the client bundle at all (that is a separate exposure problem covered in the Stripe secret key article).

Why AI-built apps ship with test keys live

You build and test in Stripe test mode for good reason: it is the safe sandbox where fake cards work and no real money moves. AI builders and starter templates lean into that. When a tool scaffolds a Stripe integration, it wires up whatever key you pasted during the build, and during the build that is almost always your test key. The app works end to end in the preview because test-mode checkout succeeds with Stripe's test card numbers.

So the integration looks finished. Checkout flows, the success page renders, the dashboard shows a payment. Nothing signals that this only works for fake cards. The step that does not happen on its own is the deliberate cutover: swapping pk_test_ and sk_test_ for the live keys in the production environment, and confirming the deployed build actually picked them up.

  • The test integration succeeds in preview, which makes the missing cutover invisible from the inside.
  • Live keys live behind a Stripe account activation step, so they are often not even available yet when the first deploy goes out.
  • Fast deploy means the public URL exists before anyone checks which mode the production build is running in. The environment variable that was right for development is simply still there in production.

Confirm which mode your deployed build is in

You can verify this on your own app in under a minute, with no payment and no attack. The publishable key is meant to be in the browser, so reading it from your own production site is a normal inspection.

From DevTools on your live URL: open the page that loads Stripe (usually checkout or pricing), open DevTools, and use the Sources or Network tab search to look for the strings pk_test_ and pk_live_ across the loaded JavaScript. Stripe.js is initialised with the publishable key, so one of these prefixes will appear. You can also search the page's network requests to js.stripe.com and the bundle for the same prefixes.

A second confirmation that needs no code reading: make one real, small purchase yourself on the production site with a real card, then check that it lands in the live view of your Stripe Dashboard (the dashboard has a test-data toggle, so make sure you are looking at live). A real payment that appears in live data is the clearest end-to-end proof. A real card that gets rejected, or a payment that only shows up under test data, points straight at a test key still in play.

  • Suspicious result: pk_test_ appears in your production build. That deployment is in test mode and will reject real cards. Treat it as a launch blocker.
  • Safer result: only pk_live_ appears, and pk_test_ is absent. Your client is initialising Stripe in live mode.
  • Important caveat: this confirms the client side only. A live publishable key on the front end paired with a leftover test secret key on the server still fails, just at a different step. The publishable prefix is a strong signal, not full proof.

Switch to live keys, and check what the switch does not cover

The fix is to set the live keys in your production environment and redeploy, then re-verify. Concretely: replace the pk_test_ publishable key and the sk_test_ secret key in your production environment variables with their pk_live_ and sk_live_ counterparts from the live section of your Stripe Dashboard. Live keys only exist once your Stripe account is activated, so account activation is a prerequisite, not an afterthought.

Then redeploy and re-run the DevTools check above against the freshly deployed URL, because an environment variable change does nothing until a new build picks it up. Confirming the running production build is the new code, not a cached or stale deploy, is the part people skip.

What switching keys does not cover: it does not verify that your webhook handler checks the Stripe signature, that your server recomputes prices from a trusted catalog instead of trusting a client-supplied amount, or that subscription entitlements are enforced server-side. Those are separate launch checks. Live keys mean payments can succeed; they say nothing about whether the payment logic around them is correct.

  • Set both keys together. A mismatched pair (live publishable, test secret, or the reverse) fails just as surely as two test keys.
  • Keep the secret key server-side only. Moving to live mode does not make it safe to expose sk_live_ in the bundle; a live secret key in the browser is a worse problem than a test key in production.
  • Redeploy and re-check. The key change is real only once a new build serves it.

Where VibeCodeGuard fits, and what it does not prove

VibeCodeGuard's Launch Check fetches your public client bundle and looks for the Stripe key prefixes in it. If it finds pk_test_ or sk_test_ in the deployed build, that is a direct, low-ambiguity signal that the production app is wired to test mode, and the scan flags it with severity and fix direction. Because the publishable key legitimately ships to the browser, this is exactly the kind of public-surface fact a scan from a clean session can confirm without any access to your code or environment.

What it cannot see is the server side. The scan reads what the browser receives; it does not know which secret key your backend holds, so a clean client-side result does not prove the matching live secret key is in place server-side. It also does not test webhook signature verification, price enforcement, or entitlement logic, all of which need code review.

Next step: open DevTools on your production checkout page and search the loaded bundle for pk_test_ right now. If it shows up, swap in your live keys and redeploy before you announce. Then run a focused Launch Check against the public URL so a leftover test key surfaces in your scan and not in a customer's failed payment.

A Launch Check that finds only pk_live_ tells you the client is in live mode. It does not confirm an end-to-end payment works, because the server secret key, webhook handling, and price logic are invisible to a public scan. Treat a found test key as a launch blocker, and treat a clean result as one signal, not a guarantee.

> launch check

Scan the public surface before launch.

Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.