← All articles

Expired API Key Provided Stripe: Causes and Fixes

Key takeaways

  • Stripe secret keys have no built-in expiry, so the 'expired API key provided' error almost always means someone rotated or deleted the key while a service still held the old copy.
  • Test and live mode keys are independent in Stripe, so a staging fix proves nothing about production.
  • To fix live mode without downtime, publish the new key to your vault, deploy consumers, then delete the old key in Stripe last.
  • Persistent errors after rotation usually come from stale copies in .env files, CI caches, old secrets versions, or Postman collections.
  • Recording each key's owner, environment, and expiry metadata at creation time turns future rotations into scheduled work instead of incidents.

Stripe returns "Expired API key provided" when the key your integration sends was revoked or replaced, most often because someone rotated it in the Stripe dashboard and an old copy is still live in a service, CI job, or local config. The fix is to find which integration is sending the stale key, replace it with the current one from your vault, and confirm test versus live mode matches. This guide maps each error variant to its root cause.

What does the 'Expired API key provided' error from Stripe actually mean?

Stripe returns Expired API key provided with HTTP status 401 when the key you authenticated with is past its expiry date. Stripe's error documentation lists this code alongside invalid_api_key (Stripe API error codes), so you get the same 401 either way and have to read the message body to tell them apart.

The distinction matters for triage:

  • Expired: the key was valid but its expiry timestamp has passed. The credential exists in Stripe; the clock ran out.
  • Invalid: the key is malformed, mistyped, or belongs to a different account. Nothing was ever going to authenticate with it.
  • Revoked: someone deleted or rolled the key in the Stripe dashboard while a service was still using the cached copy.

Expired is the only one of the three that was predictable. If you track expiry dates and ownership metadata for each key before it lapses, the fix is a rotation you scheduled rather than a production outage you discover from this error. We cover that tracking approach separately in how to track API key expiry.

Which Stripe key types can expire and which cannot?

Stripe's error text is misleading: most of the time, nothing "expired" in the calendar sense. A credential was revoked, deleted, or mode-mismatched. Knowing which type you're holding tells you which failure you're actually looking at.

Credential typeCan it expire?How it actually fails
Secret key (sk_live_, sk_test_)No built-in expiryRevoked or rolled in the Dashboard; old value stops working immediately
Restricted key (rk_live_)No built-in expiryPermissions changed, key deleted, or revoked
Publishable key (pk_live_)NoNot used for auth; failures are mode mismatches, not expiry
Webhook signing secret (whsec_)NoRotated secret fails signature verification, a different error path
Platform keys for connected accountsNoAccount disconnect or capability revocation, not time-based expiry

Standard secret keys: revocation, not expiration

A standard secret key stays valid until you roll it. The "expired" error almost always means someone rotated it in the Stripe Dashboard and a deploy somewhere is still holding the old value. The root cause is a lifecycle gap: nobody recorded who owns the key or where it's used. Tracking expiry metadata and ownership per credential is exactly what KeyGuardRail does without holding the secret value itself, and the same pattern applies when you manage API keys across multiple cloud providers.

Restricted keys and expiry settings

Restricted keys fail on permissions, not age. If a teammate narrowed scopes or deleted the key, requests return errors that look identical to expiry. Check the key's status and permission set in the Dashboard before assuming rotation is the fix.

Platform keys and connected accounts

Platform keys fail when a connected account is disconnected or loses a capability like card_payments. The error surfaces on your requests but the cause lives on the account side, so fix the relationship, not the key.

How do you find which service or integration is sending the expired key?

Check the request metadata and mode prefix

The error text alone rarely tells you who sent the bad key, so start with what the request itself reveals. Stripe's API keys documentation explains that live and test mode keys have distinct prefixes (sk_live_, rk_live_, sk_test_), and the prefix in your logs tells you immediately whether production or a sandbox is leaking the stale credential. Pair that with the request ID from your server logs and look up the originating IP, user agent, and endpoint.

Grep your deploy targets: env files, CI variables, vaults

Once you know the mode, search every place a key can hide. That means .env files on developer laptops, CI/CD secret variables, Postman collections, and the values stored in your vault. Secrets sprawl is a human problem before it is a tooling problem: keys start in a dashboard or a handoff message, then get copied into local files and dev tools where no vault ever sees them and nobody records an expiry date or an owner. Over months, those copies multiply silently, and the first signal that any of them is stale is usually this exact 401 in a production log. Grep for the key prefix across your repos and deploy configs, then diff each hit's value against the current key in the Stripe dashboard.

Any mismatch is your culprit. If the stale copy lives only in a vault, check which service last pulled that secret and when. A rotation policy without an inventory of where copies live just means the next expired key takes another hour to trace; mapping owner and location per credential, with expiry dates attached, is the metadata layer that makes this grep a lookup instead (API Key Expiration Policy Examples for Small Teams).

Ask Stripe's API which key you just used

If logs are thin, call Stripe directly. An authenticated request with the suspect key against an endpoint like /v1/charges?limit=1 either succeeds, confirming the key is valid, or returns the same authentication error, confirming it is the expired one. Restricted keys behave the same way but can also fail with a permissions error, which tells you the key is alive but scoped wrong. This binary check is faster than rotating every key and hoping the error stops.

How do you fix the error in test mode versus live mode?

Test and live mode keys are independent in Stripe. Fixing the test-mode error proves nothing about live mode, which is why a staging green light often masks a production key that expired days earlier.

Test mode: rotate and redeploy safely

Test-mode failures cost nothing, so fix them cheaply: roll the test secret key in the Stripe Dashboard, update the value in your vault (Secrets Manager, Vault, or Infisical), and restart or re-invoke the integration. If the error persists, check for a stale key in a Postman collection or CI variable, since humans moving secrets by hand is the failure mode teams report most.

Live mode: rotate without dropping in-flight payments

Publish the new live key to your vault first, deploy consumers that read it, then roll the key in Stripe last. Consumers pick up the new value on their next config refresh, so in-flight charges never hit a dead credential. Record the expiry date and owner before you roll, not after.

Restricted keys: fix the scope, not just the key

Restricted keys fail with the same error when their scopes no longer cover the call, even if the key is valid. Confirm the required permissions before rotating, and audit who owns each key and what it can touch (How Do You Audit API Key Permissions?).

Why does the error persist after rotating your Stripe key?

Because rotation only fixes the error if the new key actually replaces every copy of the old one. In practice, four places keep serving the dead key long after you rolled it in Stripe.

The stale-copy problem: where old keys hide

The usual suspects: a cached .env file on a developer's laptop, a deploy pipeline still reading an old secrets version, CI caches that snapshot environment variables, and Postman collections where secrets accumulate outside the vault. This is the same secrets sprawl pattern described above: keys handled by humans first, then copied into dev tools where nobody tracks expiry. If two people hold the key, rotating it in one service does nothing for the other.

Verifying the new key actually shipped

Confirm the deploy picked up the new value: check the environment variable in the running process, not the config file. For live-mode keys, Stripe's own guidance is to create the replacement key first, deploy it, then delete the old one, so you can verify the swap without downtime (Stripe docs on rotating API keys). If the error persists after that, the old key is still live somewhere: grep your CI logs and secrets manager version history for the old key prefix.

Track which service holds which key version in one place, with ownership and expiry metadata. That is the gap KeyGuardRail covers, and it is the same lifecycle problem we break down in API key lifecycle management tips.

How do you prevent Stripe API key expiration from breaking production integrations?

The error is a symptom. The root cause is usually that nobody tracked the key's expiry, nobody owned its rotation, and nothing noticed until a customer saw the failure. Three patterns close that gap.

Track expiry metadata, not secret values

You don't need custody of the secret to know when it dies. Record the key's ID, its environment (test or live), its scope, and its expiry date, and keep the value inside your vault. Stripe's restricted keys let you set per-resource permissions and expiration when you create them (Stripe API keys documentation), which means the metadata exists at creation time. Capture it then, not during an incident.

Assign an owner and a rotation deadline per key

Every key gets a named owner ("Payments platform", "Platform engineering") and a rotation deadline, typically 30/60/90 days for long-lived credentials, matching the policy most teams already claim to have. When a deadline approaches, the owner gets notified with enough lead time to rotate in low-traffic hours, not at 2am when checkout breaks.

Where vaults stop and lifecycle monitoring starts

AWS Secrets Manager, HashiCorp Vault, and Infisical store and distribute secrets well. None of them answers "which Stripe key expires Friday and who owns it" across providers. That's the gap KeyGuardRail covers: credential lifecycle monitoring that reads metadata only, assigns ownership, and tracks rotation deadlines without ever holding the secret value. Your vault stays the source of truth for values; the lifecycle layer stays the source of truth for time.

When should you roll keys proactively instead of waiting for the error?

Reactive rotation fixes one incident. A rotation policy fixes the pattern. Stripe restricted keys don't expire on their own, so most teams roll them only after something breaks. The framework below trades a small amount of scheduled work for fewer 2 a.m. rolls.

Signals that justify an out-of-band roll

Roll immediately, regardless of cadence, when: a key appears in a Postman collection or shared channel, a dev with access leaves the team, a third-party integration reports unexpected auth failures, or a key shows usage from an unknown IP in Stripe's logs. These are exposure events, not expiry events. Waiting for the next scheduled window extends the blast radius.

Matching cadence to blast radius

Live-mode platform keys that move money: 30 days, with a named owner and a rollback key staged before each roll. Restricted keys scoped to one integration: 60 to 90 days is defensible if scope is narrow. Test-mode keys: roll on schedule drift, not on a timer. Short-lived creds beat long cadences wherever the integration supports them; a strict 30/60/90 policy is the floor for anything long-lived, not the ceiling.

FAQ

Does Stripe automatically expire API keys, or did someone revoke mine?

Stripe secret keys have no built-in expiry, so the key did not age out on its own. The 'expired' error almost always means someone rolled or deleted the key in the Dashboard while a service still held the old copy. Check the key's status in the Dashboard, then grep your env files, CI variables, and Postman collections for the stale value.

How long do Stripe restricted keys last before they expire?

Indefinitely, unless you set an expiration date when creating the key. Restricted keys fail on permissions or deletion, not age: a teammate narrowing scopes produces errors that look identical to expiry. Confirm the key's status and permission set in the Dashboard before assuming rotation is the fix, and record expiry metadata at creation time if you set one.

Can I have more than one live secret key active in Stripe at the same time?

Yes. Stripe's recommended rotation flow relies on this: create the replacement live key first, deploy it to your consumers, verify the swap, then delete the old key last. That overlap lets you roll without dropping in-flight charges. Publish the new value to your vault before rolling, and record the owner and expiry date before the switch, not after.

Why does my webhook still fail with 'the provided api key is expired' after I rolled the key in the dashboard?

Because some consumer is still reading the old value. Usual hiding spots: a cached .env file on a laptop, a CI cache that snapshotted environment variables, an old secrets version in your manager, or a Postman collection outside the vault. Check the environment variable in the running process, not the config file, and grep CI logs for the old key prefix.