Stripe Will Now Warn You 30 Days Before a Linked Bank Account Dies. Most ACH Dunning Stacks Aren't Listening.
Stripe's new Financial Connections webhook flags a dying bank link a month out, before the debit ever fails. Here's the API and why most ACH stacks ignore it.
We flagged this gap in our ACH dunning guide: card-side tools like account updater have no ACH equivalent, because there's no card network to refresh a bank account against. What a bank-linked payment method needed instead was its own proactive signal, something that fires before the debit fails rather than after. Stripe shipped exactly that on July 29, 2026, as part of the Dahlia release — four new Financial Connections webhooks that tell you a linked bank account is about to stop working, a month before it happens. Almost nobody's dunning stack is listening for it yet.
Why a bank link dies and a card doesn't just "expire"
A card fails predictably. It has an expiry date printed on it, the network reissues a new one on a schedule, and — as we covered in our card account updater guide — Stripe can go fetch the replacement before your next charge. A linked bank account has no printed expiry. What it has instead is an OAuth grant sitting between Stripe and the customer's bank, and that grant can die for reasons that have nothing to do with the account itself: the bank's token simply times out, the bank rolls out a new authentication requirement and invalidates old sessions, the customer changes their online banking password, or the customer revokes Stripe's access from inside their banking portal without telling anyone. None of that touches the money. The account can be fully funded and still stop being debitable the moment the connection between it and Stripe goes stale.
That distinction is why this isn't just a rebrand of the ACH return codes we've already written about. A NACHA return code — R01, R02, R04, and the rest — tells you a specific debit attempt failed and why. The Financial Connections deactivation webhooks fire before any debit attempt happens at all, off a completely different signal: Stripe's own knowledge of when the underlying authorization is scheduled to expire.
Account vs. Authorization: which object to watch
| Object | What it represents | When to watch it |
|---|---|---|
| Financial Connections Account | One specific linked bank account, tied to the payment method you actually debit | You want to repair or replace that exact payment method |
| Financial Connections Authorization | The consent grant at the institution level — can cover several linked Accounts at one bank | You want a single relink prompt to fix every account a customer has at that bank, rather than one prompt per account |
Stripe's own guidance is to watch the Authorization-level event if your integration prompts "repair any account at this institution" rather than a specific payment method — most subscription businesses only have one linked account per customer, so the Account-level event is usually the one that matters, but it's worth checking which shape your billing logic actually needs before you wire up a handler.
The four events, and what each one actually tells you
- financial_connections.account.upcoming_deactivation — fired 30 days before the Account's expected_deactivation_date. This is the one to build dunning logic around.
- financial_connections.account.expected_deactivation_date_updated — fired any time the estimated date changes, including if it moves further out because the customer already relinked. Useful for suppressing a warning you already sent.
- financial_connections.authorization.upcoming_deactivation — the same 30-day warning, scoped to the institution-level Authorization instead of one Account.
- financial_connections.authorization.expected_deactivation_date_updated — the Authorization-level equivalent of the second event.
Retrieving the Account object directly gives you the same information without waiting on a webhook. The status_details.active subhash carries three fields worth checking: expected_deactivation_date, a cause (for a time-based OAuth expiry, this reads access_expired), and an action field that's set to relink_required only when Stripe expects a relink to meaningfully push the date out. That last part matters — Stripe explicitly won't recommend prompting a relink earlier than 30 days out for time-based expirations, because relinking sooner doesn't change anything if the bank's own clock hasn't started counting down yet.
A concrete case: Bank of America's one-year clock
Stripe's documentation names one institution specifically. Bank of America connections expire after one year for every linked account, full stop, and the bank is also mid-migration to a new API version that's separately invalidating older connections: accounts linked before July 1, 2025 broke on August 1, 2026, accounts linked between then and April 17, 2026 break on a rolling basis, and anything linked after April 17, 2026 is unaffected by the migration. If your customer base skews toward a large retail bank like BofA and a meaningful share pays by linked bank account, you have a predictable, dated wave of relink prompts to plan for — not a random trickle of failures to react to as they land.
Source: Stripe Financial Connections and ACH documentation; Nacha operating rules.
Lined up next to each other, the three windows say something about how little time you actually get at each stage. Ten days to confirm a micro-deposit before the whole payment method reverts to unverified. Thirty days of warning before a working connection goes dark. Up to 180 days of retry eligibility once a debit has already failed — but only for the two return codes, R01 and R09, that were ever retryable in the first place. The deactivation webhook sits in the one gap none of your existing ACH tooling covers: after the account is verified and working, before anything has actually failed.
Building the relink handler
The mechanics mirror a webhook handler you probably already have for invoice.payment_failed, just triggered earlier in the lifecycle:
- Subscribe to financial_connections.account.upcoming_deactivation (and the authorization-level event if your integration prompts by institution rather than by account) in your webhook endpoint settings, scoped to a Financial Connections-aware API version.
- Read status_details.active.action off the event payload. Only fire a customer-facing prompt when it's relink_required — some deactivations genuinely can't be prevented, and those carry action: none instead.
- Send the relink prompt through a channel your customer will actually see before the deactivation date, not after. Email works here in a way it doesn't for a same-day failed-payment notice, because you're not racing a card that's already declined — you have a known date, weeks out, to work with.
- Suppress the reminder if expected_deactivation_date_updated fires with a later date. That means the customer already relinked, through your prompt or on their own, and a second warning email just reads as spam.
- Treat a missed 30-day window as a dunning event, not a surprise. If the date arrives and the account goes inactive anyway, the resulting failed debit should route into the return-code logic from your existing ACH dunning path — the deactivation webhook is a head start, not a replacement for handling the failure when the head start gets missed.
Where this still falls short
The 30-day figure is fixed for time-based OAuth expirations, which is the case Stripe can actually predict. It isn't the case for every deactivation cause. A customer who revokes access from their own banking portal, or who closes the account, generates no advance signal at all — you only find out via the plain financial_connections.account.deactivated event, after the fact, with action: none if there's nothing to repair. Coverage also depends on the bank actually surfacing an expected date to Stripe in the first place; institutions with rolling, invisible token refresh policies may never populate the status_details.active subhash, so an empty subhash on an active account isn't a clean bill of health, it just means Stripe doesn't know yet.
None of that erases the value of the 30-day window where it does apply. It's the same category of fix as card account updater: it does nothing to change a customer's mind about your product, and it doesn't need to. It just keeps a subscriber who was never planning to leave from getting cancelled by an expired bank connection they didn't know was expiring. If you're tracking how much of your churn is genuinely voluntary versus mechanical, run your ACH-specific involuntary rate through our churn calculator before you decide how much retention-offer budget to put behind the bank-pay segment — a chunk of what looks like churn there may just be an unrelinked account waiting on a prompt nobody sent.
The customers this catches were never trying to leave in the first place, which is the opposite end of the churn problem from what CancelFlow is built for. But an accurate read on either half depends on not letting a dead bank connection masquerade as a cancellation decision it never was.
Frequently asked questions
What is a Financial Connections Account in Stripe?+
It's the object Stripe creates when a customer links a bank account through Financial Connections — the same underlying connection that powers ACH direct debit via us_bank_account and Instant Bank Payments. It carries a status (active, inactive, or disconnected) and, once Stripe knows a deactivation is coming, a status_details.active subhash with an expected_deactivation_date and a cause. An Authorization is the broader consent grant at the institution level — one Authorization can cover multiple linked Accounts at the same bank, which matters if you want to prompt a single relink rather than repairing accounts one by one.
Why do Financial Connections accounts become inactive?+
Stripe's docs list four causes: the OAuth token issued by the bank expires after a set period or from inactivity, the bank changes its authentication requirements (adding MFA, for example), the customer revokes access from their own online banking portal, or the customer closes the account outright. Only the first two are usually visible to you in advance — a closed account or a manual revocation typically shows up only after the fact, as an inactive status with action: none.
How much advance notice does the upcoming_deactivation webhook give?+
Thirty days, fixed. Stripe fires financial_connections.account.upcoming_deactivation (and the equivalent authorization.upcoming_deactivation event) 30 days before the expected_deactivation_date, and its own guidance says it won't recommend a relink prompt any earlier than that window for time-based OAuth expirations — prompting sooner doesn't meaningfully change the date, since the bank's clock hasn't run down yet.
Does card account updater cover ACH or bank-linked payments too?+
No. Card account updater and network tokens sync against Visa, Mastercard, Amex, and Discover — networks that don't exist for a bank-to-bank debit. A linked bank account has no card number to refresh; it has an OAuth grant that either still authorizes Stripe to debit it or doesn't. The Financial Connections deactivation webhooks are the closest ACH equivalent, but they only work if you build a relink flow to catch them, whereas card account updater runs silently once you flip a setting.
Stop losing subscribers today
One script tag. One function call. A live cancellation flow in under 10 minutes.
Start free trial →