A live Stripe secret key (the one that starts sk_live_) can charge cards, issue refunds, and redirect your payouts. This is the highest-stakes key you can leak. Here is the calm, in-order runbook to shut it down.
Most leaked keys cost you an API bill. A live Stripe secret key touches real money, so it sits in its own category.
The secret key can do almost anything your own backend can do. In plain terms, that means someone who has it can create charges, issue refunds to a card they control (which quietly drains your balance), read your customer and payment records, and change settings that decide where your payouts go. That last one is the nightmare: money leaving your account to theirs.
So the order is simple. Kill the key at Stripe first. Everything else (checking logs, cleaning your code, rolling the webhook secret) comes after the key is dead. Rotating is the only step that actually stops the bleeding.
Two keys, two very different risks. Stripe gives you a publishable key (pk_live_) and a secret key (sk_live_). The publishable key is meant to live in your frontend and is safe there. The secret key is not, ever. If the leaked value starts sk_live_ (or rk_live_, a restricted key), keep reading. If it starts pk_live_, that is expected to be public and there is nothing to rotate.
Six steps. The first one is the only urgent one. Do it, then work down the list.
Open the API keys page at dashboard.stripe.com/apikeys (make sure you are in live mode, not test mode). Find the leaked secret key, then:
···) next to the key and choose Rotate key.STRIPE_SECRET_KEY), your CI secrets, and your host's environment settings (Vercel, Netlify, Render, and so on). Redeploy.Confirm the old key is dead. A quick test call with the old key should be rejected with a 401 (that is the HTTP code for "not authorized"):
curl https://api.stripe.com/v1/charges -u sk_live_OLDKEY:
If that returns a 401, the leaked key is off. Good.
The key may have been used before you rotated it. Two places to look:
Also glance at your payout settings and connected bank account to make sure nothing was changed. If you see money moving where it should not, contact Stripe Support right away with a short factual timeline: when the key leaked, when you rotated it, and which activity is not yours.
The number one way this happens: the secret key ends up in the browser. A publishable key (pk_live_) belongs in the frontend. A secret key (sk_live_) must stay on your server, always.
In tools like Next.js and Vite, any environment variable prefixed NEXT_PUBLIC_ or VITE_ gets baked into the JavaScript that ships to the browser. If your secret key was named something like NEXT_PUBLIC_STRIPE_SECRET_KEY, it is in your public bundle right now. To check:
sk_live_. If it appears, anyone who has visited your site could have copied it.git log -S "sk_live_" --all --oneline. If any .env file ever appears in history, treat every key that lived in it as leaked too.The fix: move all Stripe secret-key calls to server-side code (an API route, a serverless function, your backend), and rename the variable so it has no public prefix. The browser should only ever see the pk_live_ publishable key.
Once you are stable, reduce the blast radius of the next mistake. Stripe offers restricted API keys (they start rk_live_). A restricted key can do only what you grant it, instead of everything.
The idea is least privilege: give each part of your app a key with just the permissions it needs. A key that only creates payment intents cannot also issue refunds or read your full customer list. So if it leaks, the damage is capped. Stripe recommends restricted keys over the all-powerful secret key for most integrations. Create one at dashboard.stripe.com/apikeys, grant only the resources your code touches, and swap your integration over to it.
This step gets skipped, and it matters. Your webhook signing secret (it starts whsec_) is what your server uses to prove that incoming webhook events really came from Stripe and not a forger. It is not the same as your API key, but if your .env file leaked, this secret was likely sitting right next to the key.
If someone has your signing secret, they can forge webhook events that look genuine (for example, a fake "payment succeeded" that tricks your app into shipping an order). So roll it:
···) and choose Roll secret. You can expire the old secret immediately or keep it live for up to 24 hours while you update your server code, whichever fits.whsec_ value in your server environment and redeploy.Once the key is rotated and the code is fixed, verify it actually worked. Our free scan looks at your live site passively, with no signup, and flags a secret sitting in your public bundle or a config file your server should not be handing out. It is the fastest way to be sure the sk_live_ is really gone from where the public can reach it.
Partly, and it is worth understanding the limits so you do not rely on it.
Stripe takes part in GitHub secret scanning. So a secret key pushed to a public GitHub repo can be reported straight to Stripe, and Stripe may notify you or deactivate the key. That is a real safety net, and it is why some people find out from a Stripe email before they notice the push themselves.
But it is not a guarantee, and it does not cover every leak. It will not catch a key baked into your frontend JavaScript, a key in a private repo a contractor copied, or a key posted in a chat or paste site. Assume the key was captured the moment it went public, and rotate it yourself. A Stripe email is a helpful confirmation, not the plan.
A leaked Stripe key rarely travels alone. The same .env file usually holds a database URL, an AI-provider key, and more. If one leaked, check the neighbors.
We keep dedicated, step-by-step pages for the keys we see leak most: the general leaked API key runbook, plus OpenAI, Anthropic, Supabase service role keys, and the catch-all exposed .env file guide. Building on an AI app builder? See the Lovable security checklist, the Bolt leaked-key guide, and our Claude Code security notes.
This same rotation walkthrough ships as a guided command (/lictor-rotate) inside our free, open-source Claude Code plugin. Install it with:
/plugin marketplace add Raffa-jarrl/Lictor-AI
/plugin install lictor-security-suite@lictor-ai
The same plugin runs a 48-check audit catalog over your codebase, in plain English, covering leaked keys, exposed configs, and the rest of what gets apps breached.
And because "trust us" is not evidence: we publish a reproducible benchmark. In a blind run on a 30-case stratified sample of OWASP Benchmark 1.2, the audit scored an F1 of 93.8%. That is a 30-case sample, not the full benchmark, and we say so right in the results.
Today you found out the hard way: a fraudulent refund, a Stripe email, or a strange charge. That is the worst alarm system there is, and with payments the cost lands fast.
Lictor's continuous monitoring watches your exposed surface for you, from $49/mo, so the next time a secret slips into your bundle or a config file goes public, it is a notification while it is still small, not a surprise on your balance. Sign up at app.lictor-ai.com/billing. It is run by the same person who wrote this runbook: a security engineer with 20 years in the field who does real responsible-disclosure work.
A live secret key (sk_live_) can do almost anything your own code can do: create charges, issue refunds to a card the thief controls, read your customer and payment records, and change settings that decide where payouts go. This is the highest-stakes leak because it touches real money, not just an API bill. Rotate it at the Stripe Dashboard first, then check payments and logs for anything you did not do.
Open dashboard.stripe.com/apikeys in live mode. Click the overflow menu (three dots) next to the leaked secret key and choose Rotate key. In the Expiration dropdown pick Now to kill the old key immediately (the right choice for a real leak), or a later time if an automated system needs a short grace window. Click Rotate API key, copy the new key (shown once), put it in your server environment and CI secrets, and redeploy. Confirm the old key returns a 401 error.
Sometimes, but never count on it. Stripe takes part in GitHub secret scanning, so a key pushed to a public repo can be reported to Stripe, and Stripe may notify you or deactivate it. That does not cover every case, though (a key in your frontend bundle or a private repo a contractor copied, for example). Assume the key was captured the moment it went public and rotate it yourself.
No. The publishable key is designed to live in your frontend and be visible to anyone. Only the secret key (sk_live_) and restricted keys (rk_live_) need rotating. If the value you found starts with pk_live_, that is expected and there is nothing to do.