Stripe Gave Every Payment Method the Same Decline Vocabulary. Most Dunning Stacks Are Still Reading the Old One.
Stripe added error codes that mean the same thing on a card, a wallet, or a bank debit. Here's what it fixes for dunning — and what it doesn't.
Every dunning system we've ever looked at, including the logic behind our own guides on card decline codes and ACH returns, runs on the same hidden assumption: that "why did this payment fail" is a question with a different answer format depending on how the customer paid. A card gives you decline_code. A bank debit gives you a Nacha return reason. A wallet redirect gives you whatever that specific method's integration happens to expose, which is often close to nothing. On August 26, 2026, Stripe shipped something that quietly changes that: a small set of error codes meant to describe the same failure the same way, regardless of which of Stripe's 100-plus payment methods the customer used.
It's an easy release to miss. There's no press cycle behind it, no keynote slide — it's one line in the Dahlia changelog next to a dozen other API additions that month. But if you've been extending your billing stack past cards, and most SaaS businesses now are, it's the first time Stripe has tried to give you one shared vocabulary for "should I retry this or should I ask the customer for something new" instead of a different vocabulary per rail.
What actually shipped on August 26
The changelog entry adds error codes designed to represent failure states consistently across payment method types, countries, and regions. Three of them are documented by name: authentication_failure, capability_not_active, and payment_method_restricted. They're available starting with API version 2026-08-26, and the change is purely additive — nothing existing gets removed or renamed, so an integration that ignores the new codes entirely keeps working exactly as it did in July.
That additive framing matters more than it sounds. It means Stripe isn't asking you to migrate off decline_code or Nacha return reasons — those still exist, still carry the network-specific detail your compliance logic needs, and still matter for the retry caps we covered in our Visa and Mastercard reattempt rules piece. What the new codes give you is a layer above all of that: a way to ask "is this the kind of failure that's worth trying again at all" without first branching your code on which payment method the customer happens to be using.
Why "decline code" never meant the same thing twice
The reason this needed fixing is that Stripe's failure taxonomy grew the way most platform APIs do — one payment method at a time, each with its own history and its own standards body behind it. A card decline traces back to ISO 8583 response codes that predate the internet. An ACH return traces back to Nacha's R-series codes, a completely separate rulebook with its own retry limits and its own 60-day compliance thresholds. A wallet redirect in Southeast Asia or Europe often has no equivalent standard at all — just whatever status Stripe's integration with that specific rail happens to surface, which is exactly the gap we found when Stripe turned on six new payment methods across Southeast Asia without an equivalent recurring-billing failure vocabulary to go with them.
The practical result: three semantically identical failures — a payment method the issuer has restricted, an authentication step that didn't complete, and a merchant trying to use a capability it isn't cleared for — used to look like three unrelated problems depending on the rail. Here's what that looked like before August 26, next to what it looks like now.
| Failure meaning | Old representation (card) | Old representation (ACH) | New unified code |
|---|---|---|---|
| Issuer or platform has restricted this specific payment method | lost_card, stolen_card, restricted_card | R02 — account closed (closest analog, not a real match) | payment_method_restricted |
| A required authentication step was attempted and did not succeed | No dedicated code — folded into generic authentication_required handling | No equivalent — ACH has no authentication step | authentication_failure |
| Merchant account lacks the capability to process this payment type | Not decline-based — an integration-time error, not a decline_code | Not decline-based — same | capability_not_active |
Card and ACH codes per Stripe's decline code reference and Nacha operating rules, as cited in our decline code and ACH dunning guides. New codes per Stripe's August 26, 2026 changelog.
Notice that row two and three didn't really have an old representation at all — they were either buried inside a broader status or weren't decline codes in any sense. That's the actual gap this release closes: not "Stripe renamed some codes," but "Stripe named failure states that previously had no consistent name."
What payment_method_restricted actually generalizes
On a card, "this payment method is restricted" has always meant something specific and well understood: the card was reported lost or stolen, or the issuer revoked authorization for fraud reasons, and no amount of retrying changes that — it's the exact Category 1 territory we mapped out in our decline code guide, where a single reattempt is already a network compliance violation. The problem was that this concept — "the credential itself is dead, stop trying it" — didn't exist as a named thing on any non-card rail. A restricted bank account, a frozen wallet balance, or a de-authorized mandate on a redirect-based method would each surface as some generic failure, with nothing telling your dunning logic that this was the same permanently-dead-credential situation a lost card represents.
payment_method_restricted gives you that signal regardless of rail. Get it back on any payment method, and the correct response is identical to what you'd already do for lost_card: stop retrying immediately, mark the credential dead in your own database, and route straight to a "please add a new payment method" email. The mechanism your card dunning logic already has doesn't need reinventing for wallets and bank debits — it just needs to listen for one more code.
authentication_failure isn't authentication_required
These two get conflated constantly, and the distinction is exactly the kind of thing a retry schedule gets wrong by default. authentication_required means the payment needs a verification step it hasn't completed yet — a 3D Secure challenge that was never presented, for instance. The fix is straightforward: prompt the customer through that step, often via an off-session flow Stripe already supports.
authentication_failure means that step happened and didn't work. The customer opened the 3DS challenge and closed the tab. The OTP screen timed out. The authentication provider had an outage mid-flow. Retrying the exact same charge under those conditions doesn't skip the authentication step — it just asks the customer to fail the same challenge again, or worse, silently resubmits without authentication and gets rejected a second time for the same reason. The correct response to authentication_failure is a fresh prompt with context — "your last payment attempt wasn't completed, tap here to finish it" — not a scheduled retry running in the background while the customer has no idea anything is waiting on them.
Illustrative — compiled from the payment-mix patterns in our card, ACH, and Southeast Asia payment method guides. Exact splits vary by market and customer base.
capability_not_active is a message to you, not your customer
It's worth being precise about the third code, because it doesn't belong in the same bucket as the other two. capability_not_active fires when your Stripe account tries to process a payment type or feature it hasn't been cleared for — a capability that's still pending, was never requested, or got deactivated. That's not a customer-side payment failure at all; it's closer to a configuration error, the same category as a missing API key or a malformed request. If you see it in production, the fix isn't a dunning email — it's checking your account's capability status in the Stripe Dashboard, the same status object Stripe already exposes for Connect platforms. Lumping it in with the two genuinely customer-facing codes above would be the wrong takeaway from this release, so it's worth keeping it out of your retry-decision logic entirely rather than trying to make it fit.
What to actually change in your dunning logic
- Upgrade your pinned API version deliberately, not accidentally. These codes only appear if your requests specify
2026-08-26or later. Check what version your integration pins today before assuming you're already receiving them. - Check the unified code first, the method-specific code second. Restructure your
invoice.payment_failedhandler so it looks forpayment_method_restrictedorauthentication_failurebefore falling into per-method branching. That's one dead-credential check and one failed-auth check covering every rail, instead of a parallel version of each for cards, ACH, and whatever you add next. - Don't retire your existing decline_code and Nacha mapping. The network-specific retry caps and compliance thresholds we've covered elsewhere still run on the old codes. The new ones add a triage layer on top; they don't replace the detail you need once you've decided a retry is worth attempting.
- Route capability_not_active to an engineering alert, not a customer email. It's the one code in this batch that means your integration is misconfigured, not that the customer's payment failed.
None of this changes the fundamentals of involuntary churn — a dead credential is still a dead credential, and a customer who never gets prompted to finish an authentication step is still going to lapse. What it changes is how much custom mapping code you need to maintain to tell the two apart once your subscribers are spread across cards, bank debits, and the growing list of wallets and redirects Stripe keeps adding. If you're trying to size how much of your renewal failure is genuinely recoverable once you can classify it this way, our churn calculator will turn a specific failure segment into an MRR number in about a minute. And for the subscribers whose issue was never the payment method at all — the ones who'd have cancelled on a fully working credential — that's a separate problem CancelFlow's cancellation flow is built to catch before they're gone.
Frequently asked questions
What are Stripe's new payment method error codes from August 2026?+
On August 26, 2026, Stripe shipped a changelog entry titled "Adds new error codes for payment method failures," introducing codes — including authentication_failure, capability_not_active, and payment_method_restricted — designed to report the same failure state consistently across payment method types, countries, and regions, rather than each method exposing its own opaque status string.
Do I need to upgrade my Stripe API version to use them?+
Yes. The new codes ship under API version 2026-08-26 (the Dahlia series), so your integration needs to send that version or later in the Stripe-Version header to receive them. Stripe's own changelog notes the change is additive — no existing error codes are removed — so upgrading doesn't break decline_code handling you already have in production.
Does this replace Stripe's card decline_code field?+
No. decline_code and its network-specific values — lost_card, insufficient_funds, do_not_honor, and the rest of the taxonomy we cover in our decline code guide — are unchanged and still drive Visa and Mastercard's retry-compliance categories. The new codes sit a level above that, and matter most on payment methods that never had an equivalent per-network decline vocabulary to fall back on in the first place.
What's the difference between authentication_required and authentication_failure?+
authentication_required means the transaction needs a specific verification step — a 3D Secure challenge, an OTP, an in-app approval — and hasn't gotten it yet; re-prompting the customer through that step can resolve it. authentication_failure means that step was already attempted and didn't succeed — the customer closed the challenge window, the network or provider had an outage, or it timed out — which calls for a different response than firing the identical challenge a second time.
Stop losing subscribers today
One script tag. One function call. A live cancellation flow in under 10 minutes.
Start free trial →