You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Zendesk is enforcing token expiration on global OAuth clients (existing global clients unused for ~3 months, plus all newly created ones). Once enforced, Zendesk issues short-lived JWT access tokens (typ: at+jwt) plus a refresh token — but its POST /oauth/tokens response omits the top-level expires_in unless expires_in is explicitly sent in the request.
Nango's OAUTH2 credential parser (parseRawCredentials) computes expires_at only from expires_at / expires_in in the token response, with no fallback. With expires_in absent, expires_at is stored as null, so shouldRefreshCredentials() short-circuits with no_expires_at and never refreshes the connection. The access token silently expires, then the refresh token lapses (30-day default) and the connection breaks, requiring a full re-auth.
The zendesk provider currently declares neither token_params nor refresh_params, so nothing signals Zendesk to include expires_in in the response.
Fix
Add expires_in to token_params (authorization_code exchange) and refresh_params (refresh). Zendesk then echoes expires_in, Nango computes a real expires_at, and the standard refresh scheduler takes over — self-healing existing connections on their next refresh, no re-auth required.
1800 (30 min) matches Zendesk's own default access-token TTL (allowed range 300–172800). Happy to make it configurable or adjust the value.
Evidence
Direct call to Zendesk's token endpoint (refresh grant) on a global-client connection:
withoutexpires_in → response contains only refresh_token_expires_in (no expires_in) → Nango stores expires_at: null → never refreshes.
withexpires_in=1800 → response includes "expires_in": 1800 → Nango computes expires_at and refreshes normally.
Notes
For clients that have not enabled token expiration, passing expires_in will start issuing expiring tokens; since Zendesk is enforcing expiration on global clients anyway, this aligns behavior with that rollout. Glad to gate it behind a config flag if preferred.
cc @hassan254-prog 🙏 — small 2-line zendesk provider fix, would appreciate a look when you have a moment.
Zendesk is enforcing token expiration on global OAuth clients, so it now issues short-lived JWT access tokens (typ: at+jwt) plus a refresh token — but it omits the top-level expires_in from the token response unless expires_in is passed in the request. Without it, Nango's OAUTH2 parser leaves expires_at null, shouldRefreshCredentials() short-circuits with no_expires_at, and these connections never refresh — they silently break once the refresh token lapses (~30 days).
Passing expires_in in token_params/refresh_params makes Zendesk echo it, so expires_at is computed and the standard refresh flow takes over — self-healing existing connections on their next refresh, no re-auth needed. Verified against a live global-client connection (before/after in the description). Happy to make the value configurable or adjust. Thanks!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6775
Problem
Zendesk is enforcing token expiration on global OAuth clients (existing global clients unused for ~3 months, plus all newly created ones). Once enforced, Zendesk issues short-lived JWT access tokens (
typ: at+jwt) plus a refresh token — but itsPOST /oauth/tokensresponse omits the top-levelexpires_inunlessexpires_inis explicitly sent in the request.Nango's OAUTH2 credential parser (
parseRawCredentials) computesexpires_atonly fromexpires_at/expires_inin the token response, with no fallback. Withexpires_inabsent,expires_atis stored asnull, soshouldRefreshCredentials()short-circuits withno_expires_atand never refreshes the connection. The access token silently expires, then the refresh token lapses (30-day default) and the connection breaks, requiring a full re-auth.The
zendeskprovider currently declares neithertoken_paramsnorrefresh_params, so nothing signals Zendesk to includeexpires_inin the response.Fix
Add
expires_intotoken_params(authorization_code exchange) andrefresh_params(refresh). Zendesk then echoesexpires_in, Nango computes a realexpires_at, and the standard refresh scheduler takes over — self-healing existing connections on their next refresh, no re-auth required.1800(30 min) matches Zendesk's own default access-token TTL (allowed range 300–172800). Happy to make it configurable or adjust the value.Evidence
Direct call to Zendesk's token endpoint (refresh grant) on a global-client connection:
expires_in→ response contains onlyrefresh_token_expires_in(noexpires_in) → Nango storesexpires_at: null→ never refreshes.expires_in=1800→ response includes"expires_in": 1800→ Nango computesexpires_atand refreshes normally.Notes
For clients that have not enabled token expiration, passing
expires_inwill start issuing expiring tokens; since Zendesk is enforcing expiration on global clients anyway, this aligns behavior with that rollout. Glad to gate it behind a config flag if preferred.