DEV Community

Mike Badger
Mike Badger

Posted on

Every Stripe decline code, what it actually means, and whether retrying will help

When a card payment fails in Stripe, you get a short string back — insufficient_funds, do_not_honor, authentication_required. Most teams glance at it and let Smart Retries hammer the card on a schedule.

That's the mistake. The right move depends entirely on the code:

  • Some failures clear themselves if you retry in a few days.
  • Some will never clear — and retrying them can get you fined by the card networks.
  • Many need the customer to act, so retrying the same card is pointless.

A big slice of "failed" subscription payments are recoverable — but only if you act on the reason. Here's every code that matters and what to do about it.

First: code vs decline_code

Stripe returns two fields, and they're not the same:

  • code — the high-level category, usually card_declined. Tells you almost nothing.
  • decline_code — the granular reason from the issuing bank (insufficient_funds, lost_card, do_not_honor). This is the one you act on.

Caveat: issuers don't always report the true reason — anti-fraud blocks often hide behind a vague do_not_honor. The granular code is a strong hint, not gospel.

The mental model: five buckets

Every decline maps to one action: retry, email the customer, stop, or fix your checkout.

1 — Auto-recoverable (retry will likely work)

Timing problems. The card is fine; the money wasn't there that moment, or the network hiccuped.

decline_code Meaning Action
insufficient_funds No money right now Retry — time it near payday
try_again_later Temporary issuer issue Retry in a few hours
processing_error Transient error Retry shortly
issuer_not_available Couldn't reach the bank Retry — clears fast

This is the only bucket where blind retries earn their keep.

2 — Needs customer action (retrying is pointless)

The card can't complete the charge as-is. Email the customer with the specific ask.

decline_code Meaning Ask the customer to
expired_card Card expired Update their card
incorrect_cvc Wrong CVC Re-enter card details
incorrect_number Wrong number Re-enter card details
authentication_required Bank needs 3DS / SCA Confirm the payment (see below)

The fix lives with the customer, not in your retry logic. A retry-only tool fails every one of these silently.

3 — Lost cause (stop retrying)

Card is dead, blocked, or flagged. Retrying won't help — and retrying network-flagged cards can trigger Visa/Mastercard penalties.

decline_code Meaning
lost_card / stolen_card Reported lost or stolen
pickup_card Bank wants it seized — never retry
fraudulent Flagged as fraud
revocation_of_authorization Customer told their bank to stop

Action: stop. Ask for a different card, or let it churn.

4 — Hard declines (the ambiguous middle)

The bank declined without saying why — often deliberately.

decline_code Meaning
do_not_honor Catch-all decline; often anti-fraud
generic_decline / card_declined No reason given
call_issuer Customer must call their bank
card_velocity_exceeded Too many charges, short window

Action: a couple of retries max, then email: "Your bank blocked this — a quick call to them, or a different card, usually fixes it." Many do_not_honor blocks lift in 30 seconds — if the customer knows to ask.

5 — Structural (fix checkout, not the payment)

Not about one customer — a recurring leak.

decline_code Meaning Fix
currency_not_supported Card can't be charged in that currency Offer a supported currency
card_not_supported Card type unsupported Check your Stripe capabilities
transaction_not_allowed Transaction type blocked Often regional — review where you sell

Fix it once, recover every future customer who'd hit the same wall.

The European trap: SCA and authentication_required

If you sell in Europe, this is what most US-built dunning tools get wrong.

  • Under PSD2 / SCA, many recurring EU payments need the customer to authenticate via 3D Secure.
  • When they don't, Stripe returns authentication_required.
  • Most tools retry the card — which can never succeed without authentication.
  • The fix: send a confirm-payment link (Stripe hosts the flow), not a retry.

For EU-facing SaaS, authentication_required is often a large, fully recoverable chunk that's silently written off.

One-page playbook

Bucket Do this
Auto-recoverable Time-aware retry (near payday)
Needs customer action Email the specific ask; for SCA, send a confirm link
Lost cause Stop retrying; ask for a new card
Hard decline A few retries, then "call your bank / try another card"
Structural Fix checkout config once

Takeaway

Blind retries only fix one of these five buckets. The rest need an email, a config fix, or a hard stop — so "just turn on Smart Retries" quietly leaves money on the table.

Try this: pull your last few months of failed payments and group them by decline_code. The split usually surprises people — especially how much is recoverable customer-action, not dead cards.

I'm building a tool that does this bucketing and routing automatically. Got war stories about declines, or opinions on what actually drives recovery? Drop them in the comments.

Top comments (0)