All posts

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.

MU

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.

More posts