Stripe's Payment Records API: Closing the Dunning Blind Spot for Multiprocessor SaaS
62% of enterprises now run multiple payment processors. Until Stripe's new Payment Records API, failed payments outside Stripe got zero dunning.
If every subscription charge you take runs through a Stripe PaymentIntent, this post doesn't change anything about your setup — Stripe already sees every attempt, every failure, every retry. If it doesn't — if some slice of revenue moves through a second processor for redundancy, local payment methods, or cost reasons — there's a decent chance you've been running a dunning stack that only half worked, and didn't know it.
That's not a niche configuration. It's the majority. And Stripe's Billing engine — Smart Retries, dunning emails, the customer portal, revenue recognition — was built almost entirely around the assumption that a subscription's payments all flow through Stripe's own PaymentIntent object. A charge that fails on a different processor never created a PaymentIntent at all, so none of that machinery ever ran. Stripe shipped a fix for this at the end of July: the Payment Records API, which lets you report off-Stripe payment outcomes into Billing so the same recovery logic applies to them.
Why so many SaaS companies aren't Stripe-only anymore
Nobody adds a second processor for fun — it's operationally more expensive to maintain two integrations, two reconciliation processes, and two sets of decline codes to learn. Companies do it anyway for a small set of recurring reasons: redundancy against an outage on their primary processor, coverage for local payment methods and card networks Stripe doesn't fully serve in a given country, least-cost routing that sends transactions to whichever processor charges less for that card type, and straightforward negotiating leverage that comes from not having all your volume with one vendor. 451 Research's 2025 merchant study puts 90% of merchants on at least two processing partners, with larger enterprises frequently running four or more.
Until now, none of that had anything to do with churn tooling specifically — it was a payments-infrastructure decision made by whoever owns processing relationships, often without anyone on the growth or retention side flagging that it had a side effect on involuntary churn tracking.
What Stripe Billing genuinely couldn't see before
The gap wasn't cosmetic. A subscription charge routed through a second processor produced no PaymentIntent, no invoice.payment_failed event tied to real payment state, and no entry in Stripe's revenue recognition reporting. If that charge failed, Stripe's Smart Retries had nothing to retry, because there was no failed object in its system to act on. Most teams patched around this with custom webhook handlers from the second processor and a parallel, hand-built retry schedule — or, more commonly, didn't patch around it at all, and just accepted that a chunk of failed payments silently lapsed with no automated recovery attempt.
| What Stripe Billing provides | Payment on Stripe | Off-Stripe payment, before Payment Records | Off-Stripe payment, after Payment Records |
|---|---|---|---|
| Smart Retries on failure | Yes | No — nothing to retry | Yes, once the failure is reported |
| Dunning emails | Yes | No | Yes |
| invoice.payment_failed webhook | Yes | No | Yes, via PaymentAttemptRecord |
| Revenue recognition reporting | Included | Excluded, needs manual reconciliation | Included |
| Payment method shown in customer portal | Yes | No | Yes, with logo and name |
The revenue recognition gap is the one that usually gets caught first, because finance notices unreconciled volume. The dunning gap is quieter and more expensive — it just shows up as a slightly higher involuntary churn rate that nobody can explain, because the failures behind it never generated a Stripe event for anyone to look at.
How the Payment Records API actually works
The core object is a PaymentRecord — the record of a payment's full lifecycle, whether Stripe processed it or not. When you attempt a charge on another processor, you create a PaymentRecord to represent it. Each attempt against that payment — the original try, and every retry — is logged as a PaymentAttemptRecord with its outcome: succeeded, failed, refunded, or canceled. You report a failure by referencing the existing PaymentRecord and passing the failure timestamp and reason; Stripe then treats that PaymentRecord the way it treats a failed on-platform payment, which means Smart Retries schedules a next attempt using the same timing logic it applies to card declines it processed itself.
Retries don't have to stay on the same rail. You can report a retry attempt against the same off-Stripe processor, a different off-Stripe processor, or move the customer onto a Stripe-native payment method entirely — the PaymentRecord just needs the outcome of whatever you actually attempted. This is also what unlocks the two other pieces: the customer portal can now surface off-Stripe payment methods with their real logo and name instead of showing a blank or generic entry, and unified revenue recognition reporting includes off-Stripe volume alongside Stripe-native volume instead of requiring a separate export.
None of this happened in isolation. Stripe's API changelog shows the groundwork landing across two recent releases: amount_paid_off_stripe tracking on invoices shipped in the 2026-06-24.dahlia API version, and the PaymentRecord resource with full list and retrieve support, plus reporting for payment attempts and refunds, landed in 2026-07-29.dahlia — a few weeks before this post. Stripe's Billing product team framed the release as part of a broader push to "expand multiprocessor support" for Billing, alongside separate updates to invoicing customization and AI-usage pricing models.
What to check if this applies to you
Start by pulling how much subscription revenue in the last 90 days moved through anything other than a Stripe PaymentIntent — most teams running a second processor already have this number for finance purposes, just not tagged as a churn-relevant metric. Cross-reference it against your involuntary churn rate segmented the way we described in our involuntary churn guide: cancellations where cancellation_details.reason equals payment_failed. If your off-Stripe volume as a share of total revenue is meaningfully higher than your off-Stripe share of tracked failed-payment recoveries, that gap is close to the size of the blind spot you were running.
Source: 451 Research (S&P Global), Merchant Study 2025
Implementation is on you, not Stripe — the API reports outcomes, it doesn't discover them. Wherever your integration currently calls the second processor's API to attempt a charge, add a call that creates or updates the matching PaymentRecord with the result. For a failure, that means passing the failure time and reason at the moment your webhook or callback from that processor tells you the charge didn't go through. Skipping this step means the feature exists on your account and does nothing, because Stripe still has no record that a payment was ever attempted.
Where this doesn't help
It doesn't fix anything about why the off-Stripe charge failed in the first place — that's still governed by whatever card network, bank, or local rail processed it, the same categories covered in our guides to Stripe dunning and SCA-driven authentication failures. It also doesn't retroactively recover anything: PaymentRecords only cover payments you start reporting going forward, so the historical gap in your involuntary churn data stays exactly as it was. And it's additive complexity, not a reason to add a second processor if you don't already have one — every PaymentRecord you create is one more object your integration is now responsible for keeping in sync.
What it does fix is a genuinely underappreciated source of invisible churn: subscribers who never decided to leave, whose payment failed on a rail your dunning stack was never watching, and whose cancellation looked identical in your reporting to someone who quietly stopped wanting your product. That distinction is exactly the kind of thing worth modeling with our churn calculator — plug in your off-Stripe revenue share as its own segment and see what even a partial recovery rate on those failures is worth. It's also a reminder that the highest-leverage churn fixes are frequently the ones that never touch a cancel button at all. CancelFlow handles the moment a subscriber consciously decides to leave — this is the moment upstream of that, where nobody decided anything and the subscription just quietly stopped being billed correctly.
Frequently asked questions
What is Stripe's Payment Records API?+
The Payment Records API is a resource in Stripe Billing that lets you report the outcome of payments processed outside Stripe — through a second processor, a regional gateway, or a legacy system — back into Stripe as a PaymentRecord. Each PaymentRecord holds one or more PaymentAttemptRecords describing individual attempts and their outcomes (succeeded, failed, refunded, canceled), giving Stripe a complete lifecycle view of a payment it never actually touched.
Does this mean Stripe now processes my off-Stripe payments?+
No. The charge still runs through whatever processor you already use — Adyen, Braintree, PayPal, a regional acquirer, or an in-house system. You still call that processor's API to attempt and confirm the charge. The Payment Records API is a reporting layer: you tell Stripe what happened after the fact, and Stripe's Billing engine uses that information to trigger the same dunning, revenue recognition, and customer portal behavior it already runs for Stripe-native payments.
Can Smart Retries run on a payment that failed through a different processor?+
Yes, once you've reported the failure via a PaymentAttemptRecord referencing the existing PaymentRecord. Stripe schedules a retry the same way it would for a native failed PaymentIntent — you then attempt that retry against whichever processor you choose, on the same or a different rail, and report the outcome back. The retry logic itself is Stripe's; the execution of the charge is still yours to carry out.
Do I need to be running multiple payment processors for this to matter?+
Only if some subscription revenue moves through a payment rail Stripe doesn't touch — a secondary processor for redundancy, a local gateway for a market Stripe covers poorly, or a legacy system mid-migration into Stripe. If 100% of your charges already run as Stripe PaymentIntents, none of this changes your dunning setup, since Stripe already has full visibility into every attempt.
Stop losing subscribers today
One script tag. One function call. A live cancellation flow in under 10 minutes.
Start free trial →