Secrets

Stripe keys in your frontend: live vs. restricted vs. webhook secrets

Not all Stripe keys are the same

Stripe is one of the cleaner integrations a builder deals with, but it hands you several different keys and they are emphatically not interchangeable. The whole model only works if each key lives in the right place. Get that right and Stripe is safe by design; get it wrong and you can publish a powerful credential to every visitor. Here is the full set and where each one belongs.

The publishable key (pk_)

This is the only Stripe key meant for the browser. It starts with pk_ and it is designed to be public — it identifies your account to Stripe.js and lets the client build a payment form, but on its own it cannot move money or read sensitive data. Seeing a pk_test_ or pk_live_ key in your frontend is normal and expected. That is its job.

The secret key (sk_)

The sk_ key is the powerful one. It can create charges, issue refunds, read customer data — effectively act as your account. It must only ever exist on your server, used from your backend to talk to the Stripe API. If an sk_live_ key ever appears in client code, a bundle, or a public repo, treat it as compromised immediately: roll it in the dashboard and review your account activity.

Restricted keys (rk_)

Restricted keys (rk_) are secret keys with a narrower scope — you grant them only the permissions a particular service needs. They are a good practice for backend services because a leak is contained rather than total. But narrower does not mean public: an rk_ key is still server-side only. It is a smaller secret, not a publishable one.

The webhook signing secret (whsec_)

When Stripe sends events to your backend, you verify they are genuine using the webhook signing secret, which starts with whsec_. It is used by your server to check signatures, and it never goes near the browser. Leaking it would let someone forge events that look like they came from Stripe, so it stays in server configuration alongside your secret key.

The one rule that prevents the mistake

There is a simple test: if a Stripe key does not start with pk_, it does not belong in anything the browser downloads. Publishable keys are the only public ones. Secret, restricted and webhook secrets all live server-side, in environment configuration or a secrets manager — never in client code, inline scripts, or committed config that ships.

This is normal hygiene, not a special precaution. The reason it slips is the same reason most secrets leak: the quickest path during development is to use a key right where you need it, and the browser is where the checkout lives.

Check which Stripe keys your site exposes

A pk_ key in your frontend is fine; an sk_, rk_ or whsec_ is not — and you cannot reliably eyeball a bundle to tell. An automated scan reads your pages and scripts the way a bot would and flags any Stripe key shape that should not be public. Scan your site and confirm only your publishable key is out there.

Related reading

FAQ

Is it safe to put a Stripe key in my frontend?
Only the publishable key, which starts with pk_. It is designed to be public and cannot move money on its own. Secret keys (sk_), restricted keys (rk_) and webhook secrets (whsec_) must stay server-side.
What is the difference between a Stripe secret key and a restricted key?
A secret key (sk_) has full access to your account, while a restricted key (rk_) is granted only specific permissions so a leak is contained. Both are server-side only — a restricted key is a smaller secret, not a public one.
My Stripe secret key leaked — what should I do?
Treat it as compromised right away. Roll the key in the Stripe dashboard, move the call server-side, and review your account activity for charges or refunds you do not recognize. Do not just remove it from the next build.