Stripe secret key exposed in your frontend code: rotate it, then move it to the server

Updated Oct 27, 2026 · Pewterkit Guides

If a Stripe key that starts with sk_live_ or rk_live_ appears in your website's JavaScript, treat it as compromised: rotate it in the Stripe Dashboard right away, then call Stripe only from server code that reads the new key from a secret or environment variable. Only publishable keys (pk_live_) belong in the browser.

Why a secret key in the browser is a problem

Stripe's key guide lists which keys are "Safe to expose": the publishable key pk_... is the only one marked yes. A secret key sk_... "has unrestricted permissions on all Stripe APIs", and Stripe says that if someone else gets it, "they can make unauthorized charges, access customer data, or disrupt your integration."

Anything your site sends to the browser is public. Every visitor downloads your JavaScript bundle, and anyone can open the browser's developer tools and search it. Stripe's best-practices page says it plainly: "Never embed secret API keys in applications."

AI app builders produce this mistake easily: a prompt like "add Stripe checkout" can put the whole integration, secret key included, in front-end code, because there is no server in the project yet.

Fix it in this order

  1. Rotate the key now. Stripe: "If a restricted or secret API key is exposed or compromised, rotate it immediately even if you aren't sure anyone saw it." In the Dashboard, open API keys, click the overflow menu (⋯) next to the key, choose Rotate key, pick an expiration, and copy the new value. Stripe's support steps then say to update your backend, check it works with View request logs, and delete the old key.
  2. Move Stripe calls to the server. Anything that creates a Checkout Session, a PaymentIntent, a refund or reads customers must run in server code: an API route, a serverless function, or your builder's backend functions (for example Supabase Edge Functions or Firebase Cloud Functions).
  3. Store the new key as a secret. Use your host's secrets or environment-variable settings, not a file in the repository. Many frameworks expose variables with a public prefix (such as VITE_ or NEXT_PUBLIC_) to the browser, so never give the secret key one of those names.
  4. Keep only the publishable key in the browser. Stripe.js and Elements need pk_live_..., which Stripe says "It's safe to embed".
  5. Check for misuse. Stripe's support page suggests reviewing the old key's recent activity in Workbench, refunding fraudulent charges and reversing unrecognized payouts.
  6. Redeploy and look again. Search the deployed bundle for sk_live_ and rk_live_. Zero matches is the goal.

A server function that creates a Checkout Session looks like this (Node, Stripe's library):

// api/create-checkout-session.js  (runs on the server, never in the browser)
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); // set in your host's secrets

export default async function handler(req, res) {
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [{ price: "price_123", quantity: 1 }],
    success_url: "https://example.com/thanks?session_id={CHECKOUT_SESSION_ID}",
    cancel_url: "https://example.com/pricing",
  });
  res.status(200).json({ url: session.url });
}

Test this on a preview deployment before relying on it.

Use a restricted key on the server too

Stripe recommends restricted keys (rk_...) for most server code: "Restricted API keys help you limit the potential impact of a compromise." Give each one only the permissions that part of your app needs. A restricted key is still a secret and must stay on the server.

What Pewterkit Check looks at

The check reads your home page and the script files it loads, the same files any visitor downloads, and looks for live Stripe secret and restricted keys (sk_live_, rk_live_) along with other well-known secret formats (OpenAI sk-proj-, Anthropic, AWS, GitHub, Slack and SendGrid keys, Supabase service_role keys and private keys). It shows the key masked and names the file it was in. It never uses a key it finds.

FAQ

Is my Stripe publishable key safe to show?

Yes. Stripe lists the publishable key as safe to expose: it "can't perform sensitive operations such as creating charges or reading account data." Use the live one (pk_live_) on your live site.

I deleted the key from my code. Is that enough?

Rotate it as well. Old bundles stay in caches, archives and anyone's downloaded copy, and Stripe says to treat any exposure as a potential compromise.

Will rotating the key break my live payments?

Only until the new key is in place. Stripe lets the old and new keys both work for up to 7 days when you pick a later expiration, so you can update the server, confirm payments work, and then expire the old key.

Does Stripe find leaked keys for me?

Sometimes. Stripe says that when it detects an exposed key it notifies you and may deactivate it, but also that it "doesn't guarantee detection of all exposed or compromised keys", so check your own bundle.

Sources

  1. API keys: key types, rotating keys, switching to live mode (Stripe Docs) — read 27 Oct 2026
  2. Best practices for managing secret API keys (Stripe Docs) — read 27 Oct 2026
  3. Protecting against compromised API keys (Stripe Support) — read 27 Oct 2026
  4. Customize redirect behavior: success_url and {CHECKOUT_SESSION_ID} (Stripe Docs) — read 27 Oct 2026

How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked Oct 27, 2026. Spotted something out of date? Tell us.

Related guides