OAuth Token Refresh Failures in Banking API Integrations
Consent and SCA session timers, not just token refresh, trigger the synchronized 90-day failures.
Bank connections fail on a schedule. Not randomly, on a schedule. A connection that worked fine at setup will, at almost exactly the same interval for every affected user, stop pulling transactions Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations. No error spike tied to a deploy, no correlation with when someone last logged in. Just a quiet, synchronized die-off.
Most engineering teams misread this the first time. The refresh token can be renewing itself continuously and the connection can still be dead, because something else expired somewhere the refresh call never touched.
This failure typically appears at the 90-day mark. It's a common production mystery in open banking integrations, misdiagnosed because developers track one clock (the refresh token timer), while PSD2 runs two additional independent timers: the consent expiry and the SCA session, producing the characteristic 90-day cliff Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations. Retrying the refresh or adding backoff doesn't fix the cause. It just delays the next round of support tickets.
And there are going to be more of these tickets, not fewer. That's a 427% increase lucid.now financialregulations.eu Juniper Research. More integrations means more teams walking into a failure mode that was never really about code in the first place lucid.now financialregulations.eu Juniper Research.
What follows is the three-timer model behind the 90-day cliff, and what refresh logic actually needs to look like once you're building around all three timers instead of one.
Three independent timers govern every bank connection
PSD2 is the EU mandate, dating to 2018, requiring all banks to provide APIs obsidiansecurity.com lucid.now financialregulations.eu nango.dev. The UK extended it with standardized OBIE APIs obsidiansecurity.com lucid.now financialregulations.eu nango.dev. The US caught up in October 2024 when Section 1033 finalized rules banning screen scraping and requiring API-based access instead obsidiansecurity.com lucid.now financialregulations.eu nango.dev. Canada is still rolling its version out, with Phase 1 of Open Banking Canada landing in early 2026 and Phase 2 following in mid-2027 obsidiansecurity.com lucid.now financialregulations.eu nango.dev. Different countries, same basic shape: banks have to open a door, and that door runs on OAuth.
Except it's not plain OAuth. PSD2 and Open Banking mandate Financial-grade API (FAPI) security, a spec from the OpenID Foundation built on top of OAuth 2.0 and OIDC. NextGenPSD2 comes with built-in consent management and support for four SCA models: redirect, OAuth2, decoupled, and embedded getastra.com useparagon.com.
Understanding all three timers, and building refresh and re-authentication logic around each independently, is what separates integrations that stay healthy from ones that generate recurring support load.
Timer one is the one every developer already knows: the access and refresh token pair, standard OAuth lifecycle stuff RFC 9700 truto.one Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations. For sensitive financial data, RFC 9700 (the current best-practice standard, published January 2025) recommends refresh tokens that expire within 7 to 30 days and access tokens that last just 5 to 15 minutes truto.one Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations.
Timer two is the consent expiry, and this is the one that gets missed consistently. It is the regulatory permission the user granted at setup, tracked completely separately from token state. It expires on its own schedule even if every single token refresh in that window succeeded perfectly. No refresh call touches it. There is no header you can send, no scope you can request, no retry logic that extends it.
Timer three is the SCA session, the record that the user actually proved who they were at the moment consent was granted. It has its own inactivity window and its own absolute ceiling, both set by regulation rather than by whatever the application team decides. When it lapses, refreshing a token does nothing for it either.
So a refresh call can succeed and return a valid new bearer token that is attached to a consent grant expiring on schedule underneath it. The token is fine. The permission it depends on is not.
In most PSD2 implementations, the consent timer is typically the shortest absolute timer Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations. Since a batch of users typically all authorize around the same rollout window, their consent grants all age out together, producing that synchronized cliff instead of a gradual trickle of failures Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations.
This pattern is not going to simplify over time. PSD3/PSR, for which a provisional political agreement was reached on 27 November 2025, will introduce stronger SCA requirements and standardised open banking APIs lucid.now financialregulations.eu. The three timers must each be named and tracked distinctly in any compliant integration.

Token refresh failures in production code
Token refresh failures in production are often a race condition inside timer one.
Modern banking APIs and FAPI-compliant providers issue single-use, rotating refresh tokens. Every time you refresh, you get a new token and the old one is immediately invalidated. When a dashboard loads multiple endpoints simultaneously, each concurrent 401 triggers a call to the refresh endpoint, and whichever refresh response arrives first invalidates the token the others are about to use. Whichever refresh response comes back first wins, and the others have just presented an already-invalidated token.
This never appears in development, because development is one process making one request at a time. It appears in production, where background jobs, API handlers, webhook retries, and multiple app instances all reach for the same credential simultaneously.
The evidence is not hypothetical. A security review of an open-source connector (timothy-agent/timothy, issue #764) found a refresh routine that read the credential, checked expiry, refreshed, and stored the result with no locking at all lucid.now. Two goroutines slipped past the expiry check at the same instant, both called the token exchange with the same old refresh token, and the loser either got rejected outright or the two writes raced and stored a token the identity provider had already invalidated lucid.now. The result was a connection that required manual intervention to restore lucid.now.
The same pattern appears elsewhere. The openclaw/openclaw project logged issue #26322 in 2026 describing concurrent refresh attempts across shared OAuth profiles that failed within milliseconds of each other, throwing 401 refresh_token_reused errors lucid.now. RFC 6819, in section 5.2.2.3, describes the most severe version of this: if the OAuth server detects a used-and-discarded refresh token being reused, it can revoke the entire token family, not just that one token. The user must then re-authenticate manually.
There is also a subtler failure mode where nothing looks broken. The langgenius/dify project logged 693 refresh calls in 24 hours, every one of them returning HTTP 200 from the plugin daemon, and not one of them actually persisted to the database github.com nango.dev. Some providers and proxy layers bury the real error inside the JSON body while the HTTP status code indicates success, which is a strong reason to write interceptors that read the payload instead of trusting the status line.
Provider rotation policies and recent breaking changes
Every provider implements refresh token lifetime, rotation, and revocation-on-reuse differently, since OAuth 2.0 leaves this behavior deliberately open. That means there is no single behavior to memorize, only patterns to recognize.
Two patterns matter before naming specific providers. Sliding expiry resets the clock every time the token gets used, so an actively-used connection effectively never expires; tracking last use per credential is sufficient here. Absolute expiry counts down from a fixed point regardless of activity, so even a connection you are refreshing continuously will hit its deadline; track the issue date and warn users early, because activity does not extend the deadline.
A few concrete examples of providers changing behavior recently:
Salesforce set a May 11, 2026 deadline (now passed) requiring PKCE on connected apps, and is rolling in mandatory Refresh Token Rotation starting with the Summer '26 release useparagon.com lucid.now Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations. Reusing an already-rotated token causes Salesforce to revoke the current refresh token and every access token tied to it, forcing a full re-authorization useparagon.com lucid.now Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations.
Zendesk started defaulting OAuth access and refresh tokens to expire for global clients on February 2, 2026, with local clients following by April 1, 2027 useparagon.com lucid.now financialregulations.eu Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations. For clients created on or after April 30, 2026, expiration configures itself automatically useparagon.com lucid.now financialregulations.eu Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations. Legacy clients must explicitly set expires_in, and if that field is missing, the access token never expires and no refresh token gets issued, which has significant implications for revocation useparagon.com lucid.now financialregulations.eu Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations.
Linear rebuilt its OAuth2 refresh system on April 1, 2026, and now access tokens expire in 24 hours once refresh tokens are enabled useparagon.com lucid.now nango.dev Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations. BambooHR retired its legacy OpenID Connect login for new integrations back on April 14, 2025, pushing new marketplace apps onto OAuth 2.0 useparagon.com. In the open banking world specifically, Lunar Open Banking in the EU introduced a client management API in February 2026 allowing third-party providers to update redirect URIs via a PUT request, requiring a valid eIDAS certificate, and overwriting all previously registered URIs with the new list provided lucid.now.
The security stakes are concrete. Obsidian Security documented a 2025 supply chain breach where attackers exploited Salesloft-Drift OAuth tokens to gain access to more than 700 downstream environments, a blast radius 10x greater than previous incidents where attackers infiltrated Salesforce directly obsidiansecurity.com truto.one. Refresh tokens are bearer credentials that operate independently of SSO and MFA for the full duration of their lifetime obsidiansecurity.com truto.one. FAPI-compliant providers are already moving toward mandatory rotation faster than general SaaS, and this is a leading indicator of where banking APIs are heading.
Refresh logic that handles all three timers
Token refresh, consent renewal, and SCA re-authentication must be tracked and triggered independently, because conflating them is what produces the 90-day cliff Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations.
For timer one, the token pair, refresh proactively before expiry rather than reactively after a 401 occurs. A pattern that holds up well in production triggers the refresh once 75% of the token's lifetime has elapsed. Because rotation means the newest token is the only valid one, store it atomically after every refresh. A delayed or dropped write breaks the next refresh cycle, not just the current one RFC 9700 truto.one Announcing support for OAuth refresh token grant type and OAuth access and refresh token expirations.
Timer two, consent, needs its own tracking entirely. Store the grant date separately from the token issue date, and warn users well before the absolute deadline because this clock does not slide the way token expiry can. Build the re-consent moment as a normal product interaction, not an error screen, and never let monitoring treat a consent expiry and a token failure as the same event, because the fix for one does nothing for the other.
Timer three, SCA, has its own inactivity window and absolute ceiling set by regulators, tracked apart from both token
Sources
- Linear OAuth refresh token invalid_grant — What it means & how to fix it | Nango Blog
- Banking API Integration: A Developer's How-To Guide (2026)
- How to Architect a Scalable OAuth Token Management System for B2B SaaS Integrations | Truto Blog
- How to Handle OAuth Token Refresh and Expiry at Scale
- lucid.now
- Open banking API Security: The Complete Guide in 2026
- Refresh Token Security: Best Practices for OAuth Token Protection



