Stripe · API key exposure

Stripe publishable vs secret API keys

Stripe pk keys belong in client code; sk and rk keys stay on the server. Compare test/live prefixes and what to do if a privileged key is exposed.

Published · Updated · Reviewed by SafeForProd

A Stripe publishable key (pk_...) is designed for client-side code. A Stripe secret key (sk_...) or restricted key (rk_...) must remain on a trusted server. Test versus live mode changes which Stripe environment the key reaches; it does not make a secret or restricted key safe to publish.

The prefix gives a useful first classification, but owner-side dashboard evidence should confirm the key, mode, permissions, and current status.

Stripe key types at a glance

Key typeCommon prefixSafe in browser or mobile code?Access
Publishable test keypk_test_...YesClient-side Stripe operations in a sandbox
Publishable live keypk_live_...YesClient-side Stripe operations in live mode
Restricted test/live keyrk_test_... / rk_live_...NoServer-side API permissions selected by the owner
Secret test/live keysk_test_... / sk_live_...NoBroad server-side Stripe API access
Organization keysk_org_...NoOrganization-level server access

Stripe’s current API-key documentation says only publishable keys are safe to expose outside the backend. Stripe recommends restricted API keys for most new server-side use cases because their permissions can be narrowed.

Webhook signing secrets are a different credential, managed per webhook endpoint. Do not confuse them with publishable, restricted, or secret API keys.

A normal client setup

The browser receives a publishable key:

import { loadStripe } from '@stripe/stripe-js'

const stripe = await loadStripe(
  import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY,
)

Because Vite publishes VITE_ variables into the client bundle, that variable must contain a pk_... key, never an sk_... or rk_... key.

Seeing pk_live_... in production JavaScript is not a confirmed secret leak. The key is intended to identify the Stripe account to client-side Stripe libraries.

A normal server setup

Trusted server code reads a restricted or secret key from the hosting platform’s secret store:

import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_API_KEY!)

Keep this module server-only. Do not serialize the environment variable into page props, return it from an API route, log the initialized client, or import the module through a client-side dependency graph.

For a new integration, identify the API resources and actions the component actually needs, then use a restricted key if Stripe supports the required permission set. An unrestricted secret key should not be the default merely because it is convenient.

Test mode is not a secrecy boundary

Stripe sandbox keys operate on test data and test-mode flows. That reduces production payment impact, but sk_test_... and rk_test_... remain server credentials. Publishing them can allow an unauthorized party to manipulate the sandbox, consume resources, interfere with tests, or learn about the integration.

Use test publishable keys in public test clients. Keep all restricted and secret keys server-side in both modes.

If a secret or restricted key is exposed

1. Assume compromise

Stripe’s secret-key best practices advise treating a sensitive key found in source or another exposed location as compromised. Record the key name, mode, location, and likely exposure window without copying the full value into incident notes.

2. Fix the exposure path

Common causes include:

  • a public environment-variable prefix;
  • server configuration serialized into HTML or JSON;
  • a secret committed to source control;
  • a mobile or desktop application containing a server key;
  • verbose logs, screenshots, support tickets, or CI output;
  • a shared server/client module included in the frontend build.

Remove the cause before deploying a replacement.

3. Create a replacement and deploy it

Use the Stripe Dashboard’s API keys page. Prefer a restricted key with the minimum permissions the component needs. Store the value in a secrets vault or server-only environment variable and deploy it to legitimate consumers.

Stripe supports key rotation with a grace period of up to seven days so old and new keys can coexist during a planned rollout. For an active compromise, use a shorter containment window appropriate to the incident and confirm critical services before immediate revocation.

4. Revoke the exposed key

Expire or rotate the old restricted or secret key once the replacement is working. Verify that legitimate request volume on the old key has stopped, unless evidence of active abuse requires immediate revocation.

5. Review request logs

Use owner-accessible Stripe request logs to look for unexpected operations during the exposure window. Match the review to the key’s mode and permissions. Record log-retention limits and other blind spots instead of treating an empty search as proof that the key was unused.

If live customer data, payments, refunds, payouts, or account configuration may have been affected, escalate to the appropriate incident, legal, privacy, and financial owners.

If only a publishable key is visible

Do not rotate it for visibility alone. Check:

  • that the prefix is actually pk_test_ or pk_live_;
  • that the client uses the intended test or live account;
  • that price, quantity, discount, entitlement, and fulfillment decisions are recalculated or verified on the server;
  • that webhook signatures are verified server-side;
  • that no secret or restricted key appears in the same bundle or source map.

A publishable key does not let the browser make arbitrary authenticated Stripe API requests, but it also does not make insecure application endpoints safe.

What the key cannot prove about payment security

A correct pk_... key in the browser does not prove:

  • the server independently calculates the amount and currency;
  • a user cannot buy another account’s item or change a price identifier;
  • fulfillment waits for the intended server-side payment state;
  • webhook signatures and event replay behavior are handled correctly;
  • refunds, subscriptions, coupons, and entitlements enforce the application’s business rules;
  • test and live environments cannot be mixed.

Those are server-side trust decisions. They require source, configuration, and authorized workflow evidence.

Evidence-first result states

  • Informational: a Stripe pk_... publishable key appears in client code as intended.
  • Needs verification: a token-like value or truncated prefix appears public, but its key type and status cannot be established from public evidence.
  • Confirmed: an sk_..., rk_..., or other owner-confirmed privileged Stripe credential is present in a public artifact.
  • Not tested: pricing logic, webhook verification, fulfillment, refunds, subscription state, and tenant ownership were not assessed.

What the free scan can tell you

The free scan can classify obvious Stripe key prefixes in the submitted page’s referenced assets and attach a redacted location. It does not use a discovered key, call Stripe, create payment objects, or exercise checkout flows.

What requires a Human security review

Payment authorization often spans client code, server routes, Stripe configuration, webhook handlers, and the application’s own entitlement model. A Human security review is appropriate when incorrect price, account, refund, subscription, or fulfillment decisions would have material impact.

Limitations

Stripe supports additional credentials and product-specific client secrets beyond the account API key types summarized here. Classify each value using the current Stripe documentation and owner dashboard. This guide reflects Stripe documentation reviewed on 2026-07-27.

Revision history

  • 2026-07-27: Initial draft based on Stripe’s current API-key and secret-management documentation.