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.
Top comments (0)