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, usuallycard_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.
For further actions, you may consider blocking this person and/or reporting abuse
