June 9, 2026 · 12 min read

Stripe Decline Codes: The Complete Reference + Recovery Actions

The complete reference for Stripe decline codes: what each code means, whether it's recoverable, and the exact recovery play for expired_card, insufficient_funds, authentication_required, do_not_honor, and more.

What Stripe decline codes are

When a card payment fails, Stripe returns two values that explain why: outcome.reason (Stripe's normalized classification) and decline_code (the raw issuer reason, when provided). A successful charge has neither.

These codes are not error messages for end users — they're machine-readable instructions for your billing system. Treating them as such is the single biggest lever in failed payment recovery.

Why decline codes matter for revenue recovery

A naive system retries every failed charge the same way: four attempts, fixed intervals, identical email. That approach loses money in three places:

  • Retrying hard declines (lost or stolen card) burns goodwill and triggers issuer blocks. Don't retry — email.
  • Retrying expired cards at all is wasted compute. The card will fail until the customer updates it.
  • Retrying insufficient_funds on the wrong day(T+1, T+3, T+7) misses payday windows. Recovery rates double when retries land on the 1st or 15th.

Routing each decline code to its matching play is the highest-ROI change a billing team can make. The rest of this page is that routing table.

Decline code reference table

The codes you'll see most often on a Stripe-billed SaaS book of business, what they mean, and the right next action.

Common Stripe decline codes and recommended actions
 MeaningRecoverable?Recommended actionRelated playbook
expired_cardCard past its printed expiry date.YesDon't retry. Send update-card flow.expired-card-failed, expiry-preemptive
insufficient_fundsCard valid, account didn't have the balance.YesRetry on payday (1st or 15th) before retrying weekly.insufficient-funds
authentication_required3DS / SCA challenge required by the issuer.YesEmail explainer, then retry only when customer confirms.authentication-required
do_not_honorGeneric issuer refusal with no detail.Yes — partiallyWait 24h, retry once, then contact the customer.bank-declined-generic
generic_declineIssuer declined, no useful reason.Yes — partiallyOffer an alternative payment method via email.bank-declined-generic
fraudulentIssuer suspects the charge is fraud.SometimesSend merchant-descriptor whitelist guide. Do NOT retry.fraud-block
lost_card / stolen_cardCard reported lost or stolen.NoDo not retry. Email customer for a new card.expired-card-failed (update flow)
pickup_cardIssuer wants the card confiscated.NoHard decline. Contact the customer for a new method.expired-card-failed (update flow)
card_replaced (network update)Network updater has a newer card on file.YesRetry silently. Most succeed without customer contact.card-replaced-stale
processing_errorTransient issuer or network glitch.YesRetry after 60s. Usually resolves.bank-declined-generic
high-value account, no auto-dunningAny decline on a VIP invoice held back from automation.YesPersonal outreach from a named human — never a template.untouched-vip
rep commitment outstandingCustomer promised to pay; deadline has passed.YesFollow up referencing the prior commitment by date.commitment-outstanding

expired_card

The single biggest bucket of recoverable failures — roughly 30% of involuntary churn. The customer's card has hit its printed expiry date. Retrying the same card will never succeed; you need a new card on file.

The right play

  • Prevent before failure. 7–14 days before expiry, email the customer with a one-tap update link. The card-expiring playbook recovers 60–80% of expiries this way.
  • After failure. The expired-card playbook leads with the update link, acknowledges the failed charge once, and doesn't repeat the bad news in every follow-up.
  • Enable Stripe's Card Account Updater. Free. Silently rotates cards from major issuers and catches ~30% of expiries with zero customer touch.

insufficient_funds

The card is valid; the account didn't have the balance on the day you charged. Often timing, not intent — corporate cards and consumer cards both routinely refuse on the 28th and approve on the 1st.

The right play

  • Retry near payday. Schedule the retry for the next 1st or 15th, not blindly on T+3.
  • Ask, don't assume. The insufficient-funds playbook offers the customer a retry date instead of demanding a new card.
  • Tone matters. "Want us to try again on the 1st?" beats "Your payment failed" — same outcome, dramatically better recovery rates.

authentication_required

The issuer wants the customer to approve the charge in their banking app (3DS / SCA). This is normal in Europe and increasingly common in the US. Stripe Smart Retries won't solve it — the challenge needs a human.

The right play

  • Explain 3DS in plain language. Most customers don't know what a 3DS push notification is until you tell them.
  • Retry only when ready. The 3DS playbook waits for the customer to confirm they're ready, then re-fires the charge so the push lands while they have the app open.

do_not_honor

The most frustrating code in Stripe. The issuer refused but didn't say why. It's their default catch-all and covers everything from velocity limits to soft fraud flags to plain old "the customer's bank is being weird".

The right play

  • Retry once after 24 hours. A meaningful share resolve themselves.
  • Then contact the customer. The bank-declined playbook is honest about the unknown reason and offers an alternative method.
  • Don't burn retries. Four attempts on a do_not_honor often escalates to a hard block from the issuer.

generic_decline

Stripe's catch-all when the issuer didn't return anything more specific. Treat it the same as do_not_honor: one retry, then outreach. The bank-declined playbook handles both codes with the same flow.

Fraud-related declines

The codes fraudulent, card_declined with a fraud risk score, and certain do_not_honor responses all indicate the issuer's fraud system flagged the charge. The customer's card is fine — your merchant descriptor looks suspicious to their bank.

The right play

  • Never retry blindly. Retries on a fraud flag push the issuer toward a permanent block.
  • Send the whitelist guide. The issuer-fraud-block playbook leads with the exact descriptor the customer will see on their statement (e.g. "CHASER*ACME 4.99") and one-page instructions per major issuer.
  • Snooze 5 days. Bank whitelisting isn't instant.

card_replaced (network update)

The card network has a newer card on file for the same customer — usually because the issuer reissued the card and pushed an update through Visa Account Updater or Mastercard ABU. You don't need to email anyone; you need to retry.

The right play

  • Retry silently first. The card-replaced playbook fires a retry with no customer touch. Most succeed.
  • Only email if retry fails. Then fall back to the standard update-card flow.

Recovery strategy by category

Group the codes into four behavioural buckets and the strategy becomes obvious:

 ExamplesStrategy
Needs a new cardexpired_card, lost_card, stolen_card, pickup_cardDon't retry. Send the update-card flow.
Timing probleminsufficient_funds, processing_errorRetry on payday or after a short delay. Customer doesn't need to act.
Customer-action requiredauthentication_required, fraudulent (whitelist)Email explainer first. Retry only after the customer acts.
Issuer black boxdo_not_honor, generic_declineOne retry, then offer an alternative method.

For a top-down view of how these decisions plug into a recovery system, see the failed-payment-recovery guide. For the underlying retention math — involuntary churn: definition, formula, and how to cut it. For buyer-side comparisons of tooling that automates this, the dunning management software buyer's guide.

  1. Map every code your system sees to one of the four buckets above.
  2. Set retry policy per bucket, not per code.
  3. For the recoverable buckets, run a 3–4 step recovery sequence — single-email systems convert about a third as well.

Frequently asked questions

What are Stripe decline codes?

Stripe decline codes are short machine-readable strings (e.g. expired_card, insufficient_funds, authentication_required) returned on a charge or PaymentIntent when the card issuer refuses the payment. They live on the `outcome.reason` and `decline_code` fields of the Charge object and tell you why the bank said no — so you can pick the right recovery action instead of blindly retrying.

Which Stripe decline codes are recoverable?

Soft declines — insufficient_funds, authentication_required, do_not_honor, generic_decline, processing_error, and most expired_card cases — are recoverable with the right retry timing and customer outreach. Hard declines — lost_card, stolen_card, pickup_card, fraudulent — should never be retried and require contacting the customer for a new payment method.

Does Stripe Smart Retries handle every decline code?

No. Smart Retries is built for transient soft declines like insufficient_funds and processing_error. It does not solve expired cards (needs a new card), authentication_required (needs the customer in their banking app), or fraud blocks (needs the customer to whitelist the merchant). Those require a recovery sequence — see the failed payment recovery guide.

How do I see the decline code in Stripe?

On the Charge object, check `outcome.reason` for Stripe's classification and `decline_code` for the issuer's raw reason. In the Stripe dashboard, open any failed payment and the decline reason appears in the timeline alongside the issuer's response.

What's the difference between decline_code and outcome.reason?

`outcome.reason` is Stripe's normalized classification (e.g. `generic_decline`, `card_declined`) that you should branch your logic on. `decline_code` is the more specific issuer reason (e.g. `insufficient_funds`, `do_not_honor`) when the issuer provides one. Always read both — the specific code drives the recovery playbook.

Stop losing revenue to failed payments

Chaser forecasts at-risk renewals and automates recovery — connect Stripe in under 2 minutes.

Start free