← All posts Payments

Stripe Decline Codes Explained: What Each Means and How to Recover

A plain-English guide to every Stripe decline code — what it means, whether the charge is recoverable, and the exact retry strategy for each. Built for SaaS founders tired of guessing.

When a payment fails in Stripe, you get back a cryptic code like insufficient_funds, do_not_honor, or generic_decline. Most SaaS founders see these, shrug, and move on — because Stripe's documentation describes what they mean but not what to do about them.

That's a problem, because the decline code is the single most important signal for whether a failed charge is recoverable. Retrying the wrong kind of decline wastes attempts and burns customer trust. Not retrying a recoverable one leaves money on the table.

This guide translates every common Stripe decline code into plain English and tells you exactly what to do with each: retry immediately, retry later, ask the customer to update, or write it off.

The Two Categories That Matter

Before the codes themselves, there's one framing that makes everything else click: soft declines vs. hard declines.

  • Soft declines are temporary. The card itself is valid, but something blocked this specific charge at this specific moment. Insufficient funds, bank processing errors, fraud flags that need a second look. These are recoverable — usually by retrying at a better time.
  • Hard declines are permanent. The card is dead, closed, reported lost, or the issuing bank has said "no" in a way that won't change. Retrying these doesn't help. You need a new card from the customer.

Stripe sends both categories back through the same API response, but they need opposite treatment. A retry strategy that ignores the difference either gives up on recoverable revenue or pesters customers whose cards will never work again.

Soft Declines: Retry These

These are the codes where a smart retry strategy — waiting 24 to 96 hours and trying again at a better time of day — recovers 60 to 94% of attempts, depending on the code and your customer base.

CodeMeaningStrategy
insufficient_fundsNot enough money in the account right nowRetry in 3–5 days, typically after payday
generic_declineBank refused but didn't say whyRetry 24–48 hours later, different time of day
do_not_honorBank declined for unspecified reasons (often fraud suspicion)Retry in 24–72 hours; email customer if second attempt fails
processing_errorTemporary issuer or network errorRetry immediately, then again in a few hours
issuer_not_availableIssuing bank's system is downRetry in 1–2 hours, then in 24 hours
try_again_laterBank explicitly said to try laterRetry in 24–48 hours
call_issuerBank wants to verify with the cardholderEmail the customer; retry after 2–3 days
approve_with_idBank needs additional verificationRetry in 24 hours; email customer if repeats
card_velocity_exceededToo many recent charges on this cardRetry in 48–72 hours
reenter_transactionTransaction needs to be resubmittedRetry within 24 hours

When to retry matters as much as whether to retry

A charge that fails at 11:47 PM on a Tuesday is much more likely to fail again if you retry at midnight. Bank batch processing, cardholder sleep cycles, and payroll timing all affect success rates. The difference between "retry every 6 hours" and "retry on the morning of day 3" can be 20+ percentage points on recovery rate.

Tip

For insufficient_funds specifically, retries tend to succeed most on the 1st, 15th, and Fridays — paydays in most of the US. Retrying on a Monday morning after a weekend failure is often worse than waiting until Friday.

Hard Declines: Don't Retry

These codes mean the card will never work. Retrying is wasted effort and, on the merchant side, can actually hurt your Stripe account health. Instead, immediately email the customer asking for a new payment method.

CodeMeaningStrategy
expired_cardCard is past its expiration dateEmail customer; offer update link
incorrect_cvcCVC doesn't matchAsk customer to re-enter card
incorrect_numberCard number is invalidAsk customer to re-enter card
card_not_supportedCard type not accepted by merchantAsk for different card
currency_not_supportedCard can't transact in that currencyAsk for different card or offer different currency
lost_cardCard reported lostDo not retry; request new card
stolen_cardCard reported stolenDo not retry; request new card
pickup_cardBank has flagged the card (fraud)Do not retry; request new card
restricted_cardCard has restrictions preventing chargesRequest different card
fraudulentStripe Radar or bank flagged as fraudDo not retry; manual review

The retry-then-abandon anti-pattern

A very common mistake: retrying a hard-declined card 4 or 5 times over a week, then giving up and canceling the subscription. The customer never got an email. They find out three months later when they check their bank statement and wonder why their subscription vanished.

For hard declines, the right move is immediate customer notification. Send a friendly email within an hour of the failure with a one-click link to update the card. Follow up in 3 days, then 7 days. Well-timed dunning emails convert better than silent retries ever will.

The Special Cases

3D Secure and authentication required

authentication_required means the bank wants the cardholder to verify through 3D Secure (a one-time code or app approval). This is increasingly common in Europe under PSD2 rules but is appearing more often in the US too.

You can't "retry" this silently. You have to redirect the customer to an authentication flow. If you're using Stripe Checkout or Payment Element, this is handled for you. If you're using a custom flow, you need to implement the PaymentIntent confirmation step.

Fraud signals from Stripe Radar

Codes like fraudulent, pickup_card, and some do_not_honor responses come from Stripe's fraud detection. Retrying these repeatedly can damage your Radar score and lead to more false positives on legitimate charges later.

If a legitimate customer is getting fraud-flagged, the fix isn't more retries — it's asking them to re-enter the card (which often clears the flag) or contact their bank.

How ChurnShield Handles Each Code

ChurnShield's retry engine classifies every decline code into one of three buckets and takes different action on each:

  1. Retry immediately eligibleprocessing_error, issuer_not_available. These get a retry within minutes, because the issue is transient.
  2. Retry with delayinsufficient_funds, generic_decline, do_not_honor, etc. These get retried on a schedule optimized for the code type (3 days for insufficient funds, 24 hours for generic decline, etc.) and time-of-day shifted to avoid repeating the original failure window.
  3. No retryexpired_card, stolen_card, fraudulent, etc. These trigger the dunning email sequence immediately, with copy tailored to the specific problem (an "update your card" email reads differently for an expired card vs. a fraud flag).

The result: higher recovery on the soft declines, faster customer notification on the hard ones, and no wasted retries in between. On a typical SaaS Stripe account, code-aware retry logic recovers 18–32% more revenue than Stripe's default retry schedule.

Your Own Numbers

Before you build any of this, know which codes actually show up in your Stripe account. Every SaaS has a different mix — a consumer app sees more insufficient_funds, a B2B tool sees more do_not_honor and call_issuer.

The free ChurnShield Stripe Audit shows you the top 5 decline codes in your account over the last 90 days, with a dollar amount next to each. That's usually the fastest way to figure out which of the codes above actually matter for your business.


Want to recover your own failed Stripe payments?

Connect Stripe in 2 minutes. 14-day free trial. 2x ROI guarantee.

Start Free Trial