Stripe Payment Failed? Common Reasons and Fixes
What each Stripe decline code actually means, which ones you can fix, and how to recover failed subscription payments automatically.
Muhammad Usman
June 18, 2026 · 2 min read
A failed payment does not mean the customer has no money. In the Stripe dashboards I work with, most declines fall into a few buckets, and each has a different fix. Understanding the decline code tells you exactly what to do.
Reading the decline code
Every failed charge in Stripe carries a decline_code. Find it in the dashboard under the payment, or in the API error:
// What a failed charge looks like from the API
{
"error": {
"type": "card_error",
"code": "card_declined",
"decline_code": "insufficient_funds",
"message": "Your card has insufficient funds."
}
}The common codes and what to do
- insufficient_funds: temporary. Retry in a few days, smart retries handle this well
- expired_card: send the customer a payment-update link, Stripe hosts the page for you
- do_not_honor: the bank refused without a reason. The customer must call their bank or use another card
- incorrect_cvc / incorrect_number: a typo. Ask the customer to re-enter the card
- authentication_required: 3D Secure was not completed. Re-trigger the payment so the bank popup appears again
Set up automatic recovery
In the Stripe dashboard under Billing settings, enable smart retries and the built-in dunning emails with a hosted payment-update page. This alone recovers a large share of failed subscription payments with zero code.
Handle the states correctly in your app
Your app should never cancel a subscription on the first decline. Listen to the webhook events and track the state machine:
// invoice.payment_failed -> mark past_due, keep access
// invoice.paid -> back to active
// customer.subscription.deleted -> now actually downgrade
switch (event.type) {
case 'invoice.payment_failed':
await subscriptions.markPastDue(customerId) // access stays on
break
case 'invoice.paid':
await subscriptions.markActive(customerId)
break
case 'customer.subscription.deleted':
await subscriptions.downgrade(customerId) // only now remove access
break
}Treating past_due as canceled is the most expensive billing mistake I see: it throws away customers that smart retries would have recovered automatically.
Frequently asked questions
Why does Stripe show do_not_honor?+
The customer’s bank declined without giving a reason, usually a fraud filter or account setting on the bank side. The customer needs to contact their bank or use another card.
Does Stripe retry failed payments automatically?+
Yes, if smart retries are enabled. Stripe retries at times when payment is most likely to succeed, over a window you configure, and emails the customer for you.
How much revenue do failed payments cost?+
Involuntary churn from failed cards commonly eats 5 to 10 percent of monthly recurring revenue. Retries plus dunning emails recover most of it.