Payments Preflight · Check 4 of 15

Keep test and live keys separate

Use sandbox keys (sk_test_, pk_test_) everywhere except production, and live keys (sk_live_, pk_live_) only in production env vars. Webhook secrets differ per endpoint and mode.

What it is

Stripe has sandbox (test) keys and live keys. Sandbox keys start with pk_test_, rk_test_ and sk_test_. Live keys start with pk_live_, rk_live_ and sk_live_.

Why it matters

Live keys in preview or local builds can charge real cards from a test run. Test keys in production mean real customers cannot pay. Each webhook endpoint has its own signing secret, so a mismatch breaks verification.

How to check

How to fix it

  1. Store keys only in env vars, scoped per environment.
  2. Fail fast at boot if production starts with a key that is not sk_live_, or a non-production build starts with one that is.
  3. If a live secret key was ever committed or exposed, roll it in the Dashboard.
const key = process.env.STRIPE_SECRET_KEY ?? "";
const isProd = process.env.VERCEL_ENV === "production";
if (isProd !== key.startsWith("sk_live_")) throw new Error("Stripe key mode does not match environment");

Prompt for your AI coding tool

Paste into Cursor, Claude Code, Lovable or similar. Review the diff before you accept it.

Audit how my app loads Stripe keys. Make sure no Stripe key is hardcoded or committed, that keys come from env vars, and add a startup check that throws if production is not using an sk_live_ key or a non-production environment is using one. List every place a Stripe key or webhook secret is read.

Sources

Related checks

All payments checks

Sponsor this page: get in touch.

Get new checks by email

New checks and real leak post-mortems, about once a month. No spam.