Zendesk OAuth refresh token invalid_grant — What it means & how to fix it

How to diagnose and fix Zendesk refresh token invalid_grant errors

Table of contents

If you integrate with Zendesk using OAuth 2.0, a refresh will eventually fail. It usually surfaces as invalid_grant, which can quietly break ticket syncs, Help Center ingestion, and any background job that assumes tokens “just refresh”.

This guide shows you how to spot Zendesk refresh-token failures, what typically causes them in production (especially rotation + concurrency issues), and what to do so you don’t relive the same outage next week. For broader context, see API Auth Is Deeper Than It Looks and Why is OAuth still hard in 2025?.

Spot the error

When your backend POSTs to Zendesk’s token endpoint to exchange a refresh token for a new access token:

  • Token endpoint: https://{subdomain}.zendesk.com/oauth/tokens
  • Common HTTP status: 400
  • Common OAuth error: invalid_grant

Example response payload (shape):

Zendesk OAuth error response

📋

{
  "error": "invalid_grant",
  "error_description": "Token has been expired or revoked."
}
  

Zendesk can also include an error_description that explicitly says the grant is “invalid, expired, or revoked.” Either way, the meaning is the same: the refresh token you have stored can’t be exchanged for a new access token. Retrying the exact same request will keep failing until you fix the cause.

Why did Zendesk reject the refresh token?

In Zendesk, invalid_grant is almost always a token lifecycle problem — the token is no longer usable, or your app is presenting the wrong token/tenant/client combination. The recurring root causes below are the ones that most often explain “it worked yesterday, and now everything is 401’ing”.

Most common: You’re not using the latest refresh token (rotation)

Zendesk’s refresh-token grant flow rotates tokens. After a successful refresh, Zendesk returns a new refresh token and invalidates the previous access token and refresh token.

This breaks in predictable ways:

  • You refreshed successfully but didn’t persist the new refresh_token.
  • Two workers refreshed the same connection at the same time; one “wins” and invalidates the token the other is still using.
  • Your token persistence isn’t atomic, so state gets overwritten with an older refresh token.

Once you’re holding a stale refresh token, the next refresh fails with invalid_grant.

Refresh token expired

Zendesk supports refresh token expiration and documents a default refresh-token lifetime of 30 days (configurable via refresh_token_expires_in for grant-type tokens). If your refresh token is past its expiry window, Zendesk will reject the grant and you’ll need to re-authorize.

The user (or an admin) revoked access

OAuth access in Zendesk is revocable. If a user disconnects your app (or an admin removes authorization), the stored refresh token becomes unusable. In that scenario, invalid_grant is terminal: you can’t “repair” the token — you must re-authorize.

Wrong Zendesk subdomain (tenant mismatch)

Zendesk is tenant-scoped by subdomain. If you send the refresh request to the wrong {subdomain}.zendesk.com (or you accidentally reused tokens across tenants/environments), Zendesk can’t match that token in the target instance, and refresh fails.

Client credentials mismatch

Zendesk’s refresh-token examples include client_id and client_secret. If you refresh with credentials from the wrong OAuth client (staging vs production, secret rotation not deployed, wrong env vars), Zendesk will reject the grant.

You’re trying to refresh a token that can’t be refreshed (yet)

Zendesk’s refresh-token grant flow was introduced as part of rolling out access/refresh token expiration support. Refresh tokens are issued for new OAuth token requests; older tokens may not have a refresh token associated with them. If the connection has no refresh token, the fix is to re-authorize and store the refresh token from the new grant-type flow.

Initial token request bug that “poisons” refresh later

Some teams first trigger invalid_grant when exchanging the authorization code (not during refresh). Zendesk calls out a common pitfall: using the wrong redirect parameter name — it must be redirect_uri. If the initial exchange is wrong, you may never store a valid refresh token at all, and refresh will fail later because the stored state is invalid. (This also bites teams during local development; see 3 easy ways to do OAuth redirects on localhost (with HTTPS).)

How to fix it

1) Persist the rotated refresh token (every time)

If Zendesk returns a new refresh_token on refresh, treat it as the only valid refresh token going forward.

  • Save the new refresh token immediately after a successful refresh.
  • Persist (access_token, refresh_token, expires_at, refresh_token_expires_at) together (atomically if possible).

2) Verify you’re calling the correct refresh endpoint

Zendesk has multiple OAuth-related APIs. For refresh-token grant flow, use:

  • POST https://{subdomain}.zendesk.com/oauth/tokens with grant_type: "refresh_token"

If you’re hitting a different path (for example, a management endpoint under /api/v2/oauth/...), you may be using the wrong API for refresh semantics.

3) Eliminate refresh concurrency (single-flight + locking)

Token refresh is often triggered by many events (cron syncs, user “sync now”, retries after 401s). If two workers refresh the same Zendesk connection at once, you can invalidate your own refresh token chain.

At minimum:

  • Only one refresh request in flight per connection
  • Other callers wait and then use the newly stored tokens
  • Token persistence is atomic

If this class of bug sounds familiar, see How to handle concurrency with OAuth token refreshes.

4) If it’s truly invalid/expired/revoked: trigger re-auth (don’t keep retrying)

After you’ve ruled out rotation/concurrency, wrong subdomain, and wrong client credentials, treat invalid_grant as terminal for that connection.

Practical playbook:

  • Retry once to cover rare partial failures
  • If it fails again, mark the connection as “needs re-auth”
  • Pause background syncs for that connection
  • Send the user through Zendesk’s authorization flow again

Zendesk’s own guidance notes that if refresh fails (or there is no refresh token linked), the user must re-authorize via /oauth/authorizations.

How to prevent Zendesk refresh token issues

Use this checklist to reduce “why did my Zendesk API integration break?” tickets:

  • Persist rotated refresh tokens
    Assume refresh tokens rotate; always store the latest refresh token returned by Zendesk.
  • Make refresh single-flight
    One refresh per connection at a time; atomic writes of (access_token, refresh_token, expires_at).
  • Track refresh token expiry
    Store refresh-token expiry and refresh before it expires (or alert when it’s close).
  • Store tenant identity with the connection
    Persist the Zendesk subdomain with the token set; always refresh against that tenant.
  • Clean reconnect UX
    Make “Reconnect Zendesk” a first-class action so users can fix revoked/expired tokens quickly.

Skip the headache, let Nango refresh for you

Nango's open-source API auth offers:

  • 600+ pre-built OAuth flows, including full support for all Zendesk APIs
  • Automatic OAuth access token refreshing and rotation
  • Webhooks when a refresh token is revoked, so you can warn the user instantly
  • Built-in error handling for all OAuth edge cases

If you're building a Zendesk API integration and you're tired of token lifecycle edge cases, Nango can run the refresh pipeline for you.

Focus on product features and let Nango handle the token lifecycle.

Oliver Anyanwu
Developer Relations

Stay in the loop

Bi-weekly tips, learnings & guides for product integrations

Join 5,000+ engineers, eng leaders & product managers
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.