Open your payments dashboard on a Monday morning and the failures look identical. A list of red rows, a small code in a column, a subscriber who was active last month and is now past due. The temptation is to treat them all the same way: retry the card, send an email, wait.
That uniformity is the expensive part. A decline code is not a verdict. It is a short, imperfect message from an issuing bank about a specific attempt at a specific moment. Read well, it tells you whether time, money, permission, or credentials are the actual problem. Read badly, and you will retry cards that should never be retried and abandon cards that would have paid on Thursday.
generic_decline: the bank is not saying more — yet
What it signals is very little, on purpose. The issuer returned a catch-all refusal without a specific reason. It can hide a velocity limit, a temporary risk rule, a closed account, or a block on a merchant category. It is the least informative code, and therefore the easiest to misread as an invitation to try again harder.
Do not answer with an immediate burst of retries. If the refusal was a risk rule, repeated attempts can make your merchant look worse to that issuer, not admirably persistent. Space the first retry out by at least a day, change nothing else, and watch what comes back. A second, more specific code is information. If the same code repeats across several spaced attempts over about a week, stop automated retries and ask the subscriber to check with their bank or provide another method. For a reference you can keep open while you work, see a plain-English map of Stripe decline codes.
insufficient_funds: a timing problem, not a character judgment
What it signals is simpler: the account did not have enough money at that moment. That is not disinterest, fraud suspicion, or a broken card. It is often a timing gap between a renewal date and income, rent, or another bill that cleared first.
The wrong response is to retry that evening and send a message that sounds like blame. The right response is to wait for a plausible change in balance, notify the subscriber early enough for them to act, and keep the retry window focused rather than dragging it out for weeks. One well-timed second attempt recovers more than four hopeful ones.
do_not_honor: go through the customer, not around them
This code sounds personal. It usually is not. The issuer is refusing without telling you whether the reason is a restriction on the card, a block the customer placed, or an internal rule you will never see. Treat it as a hard-ish signal: the path forward runs through the customer, not through more attempts.
Retry once after a pause if your policy allows it, then move quickly to a clear request: please ask your bank, or use a different payment method. Every extra silent retry after that teaches you nothing and risks annoying both the issuer and the subscriber.
expired_card: no amount of timing fixes a dead credential
This one is not about money at all. The credential on file has passed its date, so no timing strategy will fix it. Retry logic that keeps presenting an expired card is not persistence; it is a loop.
Stop retrying the old details. Send a specific update request, make the update link work without a password hunt on a phone, and remind once more before you cancel for non-payment. Many of these subscribers think they already updated the card on another service. A short, precise message beats a generic dunning sequence because it names the actual fix.
authentication_required: only the cardholder can take the next step
Here the bank is asking for proof, usually a challenge the cardholder must complete. The payment did not fail because funds were missing; it stalled because a step only the customer can take was never taken.
Do not keep retrying and hope the requirement disappears. Send the subscriber directly to the authentication step, explain in one sentence why their bank is asking, and give them a deadline that matches your grace period. When they complete it, retry promptly while the approval is fresh.
The triage habit
Make triage a habit, not a heroic effort. Once a week, group failures by code before you group them by customer. Ask three questions: Is this a timing problem, a permission problem, or a credential problem? Route each group differently, cap attempts by code, and review which retries actually recovered revenue. That discipline is the practical core of Swipe Again's approach to failed payment recovery.
Top comments (0)