Is insufficient_funds a hard decline? | RecoverFlow

Guide

Is insufficient_funds a hard decline?

Last updated 28 July 2026 · written by Bruce McGinley, who builds RecoverFlow

Short answer

No. insufficient_funds is not on Stripe's list of decline codes that stop a retry from being executed. Stripe will retry it, and retrying is the correct thing to do.

It is close to the ideal soft decline: the card is real, the customer is real, the account is real, and the only problem is timing. That problem fixes itself on payday.

Several widely shared articles list it as a hard decline. Check the nine code list in Stripe's own Smart Retries documentation and you will not find it there.

Where the confusion comes from

Two different distinctions get collapsed into one and the result is a mess.

The first is the card network's own idea of hard versus soft. In that vocabulary, a hard decline is one the issuer says never to retry and a soft decline is a temporary condition. That framing is real but it is not what Stripe's retry engine acts on.

The second is the operational one, and it is the one that governs your money: will Stripe actually send this charge to the network again? Stripe answers that with a list of nine specific decline codes. If your code is on it, Stripe holds the retry until a new payment method exists. If it is not, Stripe retries.

insufficient_funds is not on the list. It also is not a hard decline in the network sense. It ends up mislabelled mainly because "declined for insufficient funds" sounds final when you read it, and because plenty of blog posts copy each other rather than the documentation.

The actual list

Decline codeWhat it means and why waiting will not help
incorrect_numberThe card number itself is wrong. No amount of waiting fixes a typo.
lost_cardThe cardholder reported the card lost. The issuer will keep saying no.
pickup_cardThe issuer wants the physical card retained. Treat it as permanently dead.
stolen_cardReported stolen. Never tell the customer this is the reason.
revocation_of_authorizationThe customer told their bank to stop this specific merchant.
revocation_of_all_authorizationsThe customer told their bank to stop all recurring charges.
authentication_requiredThe charge needs SCA. Only the cardholder can complete it.
highest_risk_levelStripe Radar blocked it. Retrying does not lower the risk score.
transaction_not_allowedThe issuer does not permit this type of transaction on this card.

Nine codes. That is the whole set. Anything you see that is not in that table, including insufficient_funds, expired_card, processing_error, try_again_later and do_not_honor, will be retried by Stripe on schedule.

How to handle insufficient funds well

Because the blocker is timing rather than the card, the tools that work here are different from the ones that work for a dead card.

Give it room. This is the one failure type where a longer retry window genuinely earns its keep. Stripe's Smart Retries window can be set to 1 week, 2 weeks, 3 weeks, 1 month or 2 months. If insufficient funds is your largest bucket, the shorter windows may be ending the schedule before the customer's next pay date.

Let Stripe pick the days. Smart Retries times the attempts with a model trained across the network, and payday timing is precisely the kind of pattern that benefits from data you do not have.

Be careful with the email. Unlike a card update request, this one carries a small social cost. The customer knows why it failed. A cheerful multi step sequence about updating their payment details reads badly when the actual situation is that money was tight this week. One clear message, no urgency theatre, and let the retries do the work.

Do not treat it as a churn signal on the first failure. It frequently is not. It becomes one when it repeats across several cycles.

Checking your own

On a failed PaymentIntent, the field is last_payment_error.decline_code. In the Dashboard, open an unpaid invoice and read the last attempt.

If you want to look up any code without memorising the list, we publish a free decline code lookup covering 48 codes, which marks the nine that block retries and flags the four you should never name in an email to the customer.

Questions people actually ask

Is insufficient_funds a hard decline?

No. It is not on Stripe's list of nine decline codes that prevent a retry from being executed, and the underlying condition is temporary. Stripe will retry it and that is the right behaviour.

How many times will Stripe retry insufficient funds?

It follows your Smart Retries settings like any other retryable code. Stripe's recommended default is 8 attempts within 2 weeks, with the window configurable up to two months.

Should I email a customer whose payment failed for insufficient funds?

One clear message is fine. A multi step sequence pressing them to update their card is not, because their card is not the problem. Give the retry schedule room to catch their next pay date.

What is the difference between a hard and soft decline?

A soft decline is a temporary condition worth retrying; a hard decline is one the issuer will not approve again. In practice, the distinction that governs your Stripe account is narrower: nine specific decline codes stop Stripe executing retries, and everything else gets retried.

Which decline codes are hard declines in Stripe?

incorrect_number, lost_card, pickup_card, stolen_card, revocation_of_authorization, revocation_of_all_authorizations, authentication_required, highest_risk_level and transaction_not_allowed. Those are the nine where Stripe will not execute a retry until a new payment method is added.

Where this came from

Checked against primary sources on 28 July 2026. If Stripe changes something and this page has not caught up, tell us and it gets fixed.

If you would rather not build this yourself

RecoverFlow watches your Stripe account for failed subscription payments, stops retrying the ones that cannot succeed, and emails the customers whose card simply needs replacing. It charges 25% of what it actually recovers, with a $29 monthly floor, and nothing at all if it recovers nothing.

It is early. It is run by one person. If Stripe's own free retry settings are enough for you, use those instead, and there is a page on this site that says exactly when that is the right call.

See how the pricing works