Skip to content

fix(providers/zendesk): request expires_in so tokens can be refreshed#6763

Open
jovinual wants to merge 4 commits into
NangoHQ:masterfrom
jovinual:fix-zendesk-request-expires-in
Open

fix(providers/zendesk): request expires_in so tokens can be refreshed#6763
jovinual wants to merge 4 commits into
NangoHQ:masterfrom
jovinual:fix-zendesk-request-expires-in

Conversation

@jovinual

@jovinual jovinual commented Jul 14, 2026

Copy link
Copy Markdown

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 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:

  • without expires_in → response contains only refresh_token_expires_in (no expires_in) → Nango stores expires_at: null → never refreshes.
  • with expires_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.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@jovinual

Copy link
Copy Markdown
Author

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zendesk: connections with expiring tokens (global OAuth clients) never auto-refresh — expires_at stays null, silent breakage after ~30 days

1 participant