2
1 Comment

The Stripe decline codes most founders never look at - and what they're actually telling you

Most SaaS founders set up Stripe, turn on Smart Retries, and never look at decline codes again.
That's leaving a lot of money on the table.

Here's what's actually in those codes - and what each one is telling you to do.

The two categories that matter

Every failed payment falls into one of two buckets.

Bucket 1: Timing problems

The customer has money. The card is valid. Something temporary got in the way.

  • insufficient_funds : money is coming, retry later
  • processing_error : technical glitch, retry soon
  • network_error : connectivity issue, retry fast
    For these, Smart Retries work. Timing is everything.

Bucket 2: Action problems

No retry will ever fix these. The customer needs to do something.

  • expired_card : that card is gone forever
  • card_not_supported : wrong card type for subscriptions
  • do_not_honor : bank blocked it, customer needs to call or use a different card
  • card_velocity_exceeded : fraud protection triggered, customer needs to act

For these, you need to send a message. The right message, for the exact reason.

The codes that sit in the middle

Some codes are genuinely ambiguous - and this is where most dunning logic breaks down.
do_not_honor is the classic example. It looks temporary. It often isn't. Issuers use it as a catch-all for a dozen different situations, and retry logic handles it poorly.
The practical fix: treat do_not_honor as a soft Bucket 2. Send a gentle message first, retry once, then escalate communication if unresolved.
card_velocity_exceeded is similar - looks like a fraud flag, but often the customer just hit their daily limit. Day 1 outreach with a clear explanation converts well here.

The one code you should never contact

fraudulent - do not send anything. Ever.
If Stripe has flagged a payment as fraudulent, reaching out to that customer can cause more harm than the lost revenue. This is the one case where silence is the right answer.

What this means in practice

Most dunning setups apply the same retry cadence to everything. That's why recovery rates stay low.
The founders who crack this treat each decline code as a different diagnosis — not just a failed payment.

  • Bucket 1 gets smart retry timing
  • Bucket 2 gets direct customer communication on day 1
  • Ambiguous codes get a fallback approach
  • Fraud codes get nothing

Do you actually look at your decline code breakdown? Or is it mostly set-and-forget?

(Building DunnAI — it reads every decline code and sends the right response automatically. Free diagnostic at getdunnai.com)

on April 7, 2026
  1. 1

    Full disclosure: I'm Avery Lin (avrlin). I've been packaging a small Stripe dunning email pack (decline-code matrix → copy) with AI assistance, so take this as adjacent interest, not neutral advice.

    Your bucket split matches what I keep seeing in the wild: insufficient_funds / processing_error / network_error want retry-aware timing; expired_card / card_not_supported need a calm update-card path; do_not_honor is the ambiguous one — soft outreach first, one retry, then escalate; fraudulent should get silence. One generic "payment failed, please update billing" note burns recovery on the easy buckets and feels cold on the hard ones.

    Curious whether founders here actually route day-1 copy by decline family yet, or still Smart Retries + one email for everything?