OAuth refresh token grant type and OAuth access and refresh token expiration | The place for Zendesk users to come together and share
Skip to main content
Accepted

OAuth refresh token grant type and OAuth access and refresh token expiration

Related products:Admin center
  • April 30, 2025
  • 28 replies
  • 70 views

Shawna James

Dear Community, 

 

We have recently released support for the OAuth refresh token grant type  for more robust and flexible API authentication. We are thrilled to have you start trying this feature and hear your thoughts.

 

Please share your feedback with us here in the comments below to help us improve this feature! We will be moderating this thread to respond when needed. We appreciate you taking the time to share your insights. 

28 replies

  • May 26, 2025

Hi! 

 

I'm trying to migrate to the refresh_token flow, but I'm having an issue with this.

 

Every time I try to refresh the token it fails with the error:

 

{"error":"invalid_grant","error_description":"The provided access grant is invalid, expired, or revoked (e.g. invalid assertion, expired authorization token, bad end-user password credentials, or mismatching authorization code and redirection URI)."}
 

My code looks the same as the “Example Node.js refresh token grant flow” code here: https://developer.zendesk.com/api-reference/ticketing/oauth/grant_type_tokens/, and I'm validating the data I'm sending that all the values are actually being filled in correctly.

 

I've also tried including the “redirect_uri” as a parameter even though it isn't listed in the example, but it's still not working.

 

Not sure how else I can debug this? The details I'm testing with are:

 

https://{ZENDESK_SUBDOMAIN}.zendesk.com/oauth/tokens

{
   "grant_type": "refresh_token",
   "refresh_token": "{ACTUAL_REFRESH_TOKEN}",
   "client_id": "{ACTUAL_CLIENT_ID}",
   "client_secret": "{ACTUAL_CLIENT_SECRET}",
   "scope": "read write tickets:read tickets:write",
   "expires_in": 300,
   "refresh_token_expires_in": 2592000
}
 

Thanks!


Jos11
  • June 2, 2025

This appears broken. It always returns this error:
 

{
    "error": "invalid_grant",
    "error_description": "The provided access grant is invalid, expired, or revoked (e.g. invalid assertion, expired authorization token, bad end-user password credentials, or mismatching authorization code and redirection URI)."
}

Caroline Kello

Thanks to both of you as you helped us identify a bug that we're addressing right now. I'll be back to update this thread once we have a fix out. 


Caroline Kello

Fix is released! @jos11 and @phil42 if you could please test and confirm 🙏


Jos11
  • June 4, 2025

Thank you Caroline, it appears to work now.


  • June 4, 2025

Same here Caroline, looks like we're all good now! Thanks for getting fixed so quickly!


  • June 6, 2025

Hello Shawna/Caroline,

 

As for new authorisation requests, the new refresh_token flow needs to be implemented and needs to be used for further usage.

But for existing tokens that are authorised before this release, what should we need to do for these tokens?
Does the tokens work without any issues, since there is no expiry for the tokens? Or Do we need to reauthorise those accounts and generate a new pair of tokens and use those?


  • June 6, 2025

Hello Shawna/Caroline,

 

As mentioned in the post the new authorisation requests must comply with the new refresh_token flow.

But what will happen for the tokens that are authorised before this? Will it work without any issues (or) do we need to reauthorise those accounts with the new flow and use the new token for the API requests?


Caroline Kello

We will be giving all tokens an expiration if they don't currently have one, which is why you have this period to adopt the refresh token flow to make sure you're prepare to handle it gracefully. When the access token expires, you'll be able to use the refresh token to get a new access token without having to ask the user to sign in again. If the refresh token expires then the user will be prompted to sign in again in order to issue a new access token.


Jos11
  • June 10, 2025

Hi Caroline,

People who authenticated before this change was made never received refresh tokens. Do you currently have a date on when the old auth tokens will be changed to expired? Then I'll make sure we let everyone reauthenticate before that time.

Thank you


  • June 10, 2025

@caroline13  how will be able to get the refresh tokens for the existing access tokens? Will there be a dedicated endpoint where we can supply an access token and get back the refresh token?


  • June 10, 2025

Hello Caroline,

Thank you for the response. But currently the old tokens doesn't have any refresh tokens. This is the behaviour in the old OAuth process as well. If I don't have any refresh tokens for the users, how can I refresh the expiring token?


Caroline Kello

On September 30, “old” access tokens tied to global OAuth clients, will be given an expiration date (that “new” tokens created after this release already have) and the expiration will be enforced for both old and new. Old access tokens won't have a refresh token, meaning those users will need to reauthenticate. We've articles here and here

Access tokens tied to “local” OAuth clients have a separate deadline in April 2026. 


tun11
  • June 26, 2025

Hi @caroline13 

If we currently use oauth client and api token to generate bearer token like this instruction, how do we handle this refresh token? 

 

We are currently using this curl to generate bearer token.

curl https://{subdomain}.zendesk.com/api/v2/oauth/tokens.json \
  -X POST \
  -v -u {email_address}/token:{api_token} \
  -H "Content-Type: application/json" \
  -d '{
    "token": {
      "client_id": 223443,
      "scopes": [
        "tickets:read"
      ]
    }
  }'

Dan77
  • Employee
  • June 27, 2025
Hi Tun, 
 
The refresh token flow gives a way to both have tokens expire (more secure) and not cause disruptions to users by asking them to re-authorize the OAuth app when the token does expire (better user experience). Since /api/v2/oauth/tokens is an API only endpoint, there is no user authorization performed and it isn't necessary to use the refresh token flow for these tokens. When one of these tokens expire you can simply do the same POST request to https://{subdomain}.zendesk.com/api/v2/oauth/tokens.json to get a new access token or preemptively monitor the expiry of the token and create a new token to rotate in before it actually expires. That said, any token with an associated refresh_token can use the refresh token grant detailed in articles here and here.

tun11
  • June 27, 2025

Hi @dan77 ,

Thanks for quick response! This might be questions for someone else in zendesk. 

  • Do you know if Zendesk is going to enforce expiration date for this /api/v2/oauth/tokens api path too? or is it only for those Oauth with generated refresh token type? 
  • How can I check my existing access token has expiration like you mentioned here? 

https://{subdomain}.zendesk.com/api/v2/oauth/tokens.json to get a new access token or preemptively monitor the expiry of the token and create a new token to rotate in before it actually expires. That said, any token with an associated refresh_token can use the refresh token grant detailed in articles here and here.


Dan77
  • Employee
  • July 1, 2025
Yes, expirations will be enforced on tokens created via /api/v2/oauth/tokens as well. As of right now, no tokens created through that endpoint will have expirations. Only tokens created via /oauth/tokens where the request includes an expires_in param will have them set. If a token is created with an expiry, the expiration will be in the response. More details are included in the articles I mentioned before.
 
No tokens should have expirations except for those you explicitly set, but to check an existing token you can use the Show Token endpoint described here. You may need to use the List Tokens endpoint to get the id if you don't have that stored already

Jordan18
  • July 10, 2025

Hi! The sorting on the new tokens page (https://***.zendesk.com/admin/apps-integrations/apis/oauth-clients/*****/tokens) is difficult to parse. 

 

It's hard to explain in words, but from page to page, it goes oldest to newest. However, within each page, it goes newest to oldest, making for a very confusing scrolling experience. There's also no ability to sort or filter by token creation date. 

 

We have one Oauth client with 15 pages of tokens, so the experience is very confusing. 


Max McCall
  • Product Manager
  • July 11, 2025

@jordan18 thank you for pointing this out. It seems we have a bug and will work on a fix to correct sorting behavior.


  • July 28, 2025

POST /api/v2/any_channel/push
When calling this endpoint, will the OAuth token also have an expiration date set and become unusable?
 

If set, Is there a way to know when the expiration date is?
If I edit the admin UI from Admin Center, can I get a new access token before it expires?
Admin Center > Apps and integrations > APIs > ChannelAPI


  • July 29, 2025

Does anybody know if the access token used for the channel framework (POST /api/v2/any_channel/push.json) is also affected by this change? 

And if so, how that token token could be refreshed?

 

I talking about the access token which we obtain from zendesk when the integration account is created or edited: https://developer.zendesk.com/documentation/channel_framework/understanding-the-channel-framework/push/#obtaining-an-access-token   


Caroline Kello
Hi folks, Channels and its corresponding endpoints are not impacted by the refresh token flow at this point. 

Dmitry18
  • August 11, 2025

Hi folks! I'm getting the same error as Phil and cannot figure out a way to solve it. 

I'm trying to introduce refresh token usage into my Zendesk OAuth workflow, but I'm encountering an "invalid_grant" error. I'm able to retrieve the token and use it, but not able to utilize refresh token. 

 

I'm using this doc page as a reference: https://developer.zendesk.com/api-reference/ticketing/oauth/grant_type_tokens/#create-token-for-grant-type

 

In my authorization call, I'm passing both `expires_in` and `refresh_token_expires_in`, similar to the example in the docs:

const tokenResponse = await axios.post(
  `https://${ZENDESK_SUBDOMAIN}.zendesk.com/oauth/tokens`,
  {
    grant_type: "authorization_code",
    code: req.query.code,
    client_id: ZENDESK_CLIENT_ID,
    redirect_uri: REDIRECT_URI_PKCE,
    scope: "read write",
    code_verifier: CODE_VERIFIER,
    expires_in: 86400,
    refresh_token_expires_in: 604800,
  },
  { headers: { "Content-Type": "application/json" } }
);

By using this flow, my token start to expire. 

 

When the token expires, I attempt to refresh it using the suggested structure from the docs:

const tokenResponse = await axios.post(
  `https://${ZENDESK_SUBDOMAIN}.zendesk.com/oauth/tokens`,
  {
    grant_type: "refresh_token",
    refresh_token: refresh_token,
    client_id: ZENDESK_CLIENT_ID,
    client_secret: ZENDESK_CLIENT_SECRET,
    scopes: "tickets:write",
    expires_in: 86400,
    refresh_token_expires_in: 604800,
  },
  { headers: { "Content-Type": "application/json" } }
);

However, I'm receiving this error:

{
  "error": "invalid_grant",
  "error_description": "The provided access grant is invalid, expired, or revoked (e.g. invalid assertion, expired authorization token, bad end-user password credentials, or mismatching authorization code and redirection URI)."
}

What am I doing wrong? Based on the documentation that I've seen, I do not see any issues with approach. Is the refresh token documentation accurate?


Caroline Kello
  • Product Manager
  • August 12, 2025

Hey Dmitry, we're not seeing anything obviously wrong on our end and we already fixed the underlying issue that Phil was running in to. I'm going to create a ticket for you so that we can gather more info and see if we can figure it out. 


Anya13
  • September 2, 2025

Looking at the updated docs in https://developer.zendesk.com/api-reference/ticketing/oauth/grant_type_tokens/, if expires_in for the access token request is set to null, does that mean there is an option for it never to expire, or will it always be calculated as the lesser of 2 days or refresh_token_expires_in?