"Invalid access token" after launching reference implementation of Zoom App

I have installed a development instance of the reference implementation of a new Zoom App by following the instructions here: https://marketplace.zoom.us/docs/beta-docs/zoom-apps/quickstart/. I have changed nothing in the code; I have set values in the three .env files as instructed. All three Docker containers start successfully. However, when I install and attempt to run the Zoom App in my Zoom client (which I’m logged into), I get an “Invalid access token” error in my backend container logs. I have no idea why, and since this is just reference-impl code, I’m in the dark as to how to tackle this issue. Specific steps:

  • From the App Marketplace page for my Zoom App (the page where we specify credentials, URLs, etc., for dev and prod environments), “Local Test” tab, I click “Add” to install my app
  • Click “Allow” on the /authorize page that opens in a new browser window; I now see the Zoom App in my Zoom client under “My Apps”
  • In my Zoom client, I click the app, which opens it, but with a blank page
  • In my “backend” Docker container logs, I see this: Zoom API Proxy => GET /v2/users/me -> [401] {"code":124,"message":"Invalid access token."}

I’d appreciate any suggestions anyone might have as to pushing past this roadblock.

Also, looking in my code, it seems I’m running v0.13 of the Zoom Apps SDK. But elsewhere I’ve seen reference to a v0.14. But I have no idea where to get this elusive v0.14, so any suggestions about that would be great too - and might solve my issue. Many thanks.

Further to my post above: first - the current SDK version is indeed 0.14; I upgraded but this didn’t fix the error. Second: the offending code is this, from /backend/api/zoomapp/controller.js:

if (req.session && req.session.user) {
  // 1. Get user data from session
  console.log('1. Get user data from session')
  const user = await store.getUser(req.session.user)
  const { expired_at } = user

  if (Date.now() >= expired_at - 5000) {
    // 2. efresh tokens five sec before expiry
    try {
      const tokenResponse = await zoomApi.refreshZoomAccessToken(

      await store.updateUser(req.session.user, {
        accessToken: tokenResponse.data.access_token,
        refreshToken: tokenResponse.data.refresh_token,
        expired_at: Date.now() + tokenResponse.data.expires_in * 1000,

      console.log('2. Access token was refreshed')
    } catch (error) {
      return next(error)

The call to zoomApi.refreshZoomAccessToken() causes the error, which is a 400 server response. In that response, the pertinent bit seems to be: data: { reason: 'Invalid Token!', error: 'invalid_grant' }. These forums and others have lots and lots of “invalid_grant”-related posts. I’m still in the dark, though, since this is the reference implementation which is giving me this error. …Any help welcome. Thanks again.

Some suggestions to check

  • You have created an .env file from the .env.example and filled in details there not the example file (also fully rebuild docker container with .env changes)
  • The ZOOM_APP_REDIRECT_URI matches the marketplace settings “Redirect URL for OAuth” (development version not production version)
  • The correct features and scopes have been enabled in marketplace settings (can try enabling all of them for now if you are unsure)
  • Updated “OAuth allow list” in marketplace settings (ngrok public url if you are using ngrok)

Also appreciate the callout to the current reference app and docs. We agree that it can currently be difficult to truly be a “quick” start.

Thanks @leon.cheng ! I’ve still got the data: { reason: 'Invalid Token!', error: 'invalid_grant' } error. Regarding your points:

  • Already had proper .env files (the reference implementation has three), and had double-checked the values in each.
  • Regarding the ZOOM_APP_REDIRECT_URI value, the reference impl has it commented out with a note that the value “will be set automatically by docker-compose, uncomment if not using docker-compose” (I am using docker-compose). However, I went ahead and uncommented this line and set ZOOM_APP_REDIRECT_URI to match my development Redirect URL for OAuth.
  • Enabled every feature and every scope
  • I have one URL, my PUBLIC_URL value (format: https://my-ngrok-url.tld, with no path after the protocol and hostname), in “OAuth allow list”.

The problem remains exactly as before. I’ve rebuilt twice just to make sure I was picking up all of these changes. From poking around these forums and others, I’ve found that this issue may be related to the fact that I’ve uninstalled, then re-installed, my Zoom App a few times; that may be confusing your APIs into thinking that the token I have is “invalid”. That may be a red herring, or not; I don’t know, and anyway, I’m not sure why zoomApi.refreshZoomAccessToken() wouldn’t be able to handle this eventuality. So, I look forward to what I hope are other suggestions you might have here.

Separately, if someone on your end would like some help in getting the reference implementation to a place where developers can just install it and go, please let me know - I’m happy to help (if nothing else, just by providing a list of issues I’ve previously found with its docs, comments, and code, if nothing else). Thanks again!

Whoops you’re right the ZOOM_APP_REDIRECT_URI is commented out. I meant to ask if you checked your .env values for ZOOM_APP_CLIENT_ID and ZOOM_APP_CLIENT_SECRET.

I have not heard of uninstalling and reinstalling the Zoom App confusing the APIs. There are indeed some changes that require a Zoom App to be reinstalled to take effect, so it is good that you tried it. Currently, I am out of suggestions, but will ask around a bit to see if there are other things to try.

@evgeny.balashov Do you want to track feedback on the quickstart tutorial and reference app?

Another suggestion:

  • Check that your ngrok url has not changed. Randomly generated ngrok urls made on a free account will change to a new randomly generated url if the running instance is closed.

Thanks @leon.cheng - So, should I re-comment-out the ZOOM_APP_REDIRECT_URI value in my .env? And yes, I checked and double-checked the ZOOM_APP_CLIENT_ID and ZOOM_APP_CLIENT_SECRET (and ensured that I was using dev values, not prod ones). Also: no, ngrok URL hasn’t changed; I have a dedicated one. Thanks for your help on this - it’s a 100% blocker for me right now, and remains so.

And @evgeny.balashov - yes, to confirm: happy to help with this, if it’d be helpful to the Zoom Apps team and ofc to other devs.

Did you get a resolution here @avocado876?

I did, although it just “started working,” and I wasn’t sure why, and am still not. That’s always a slightly disconcerting way for problems to be resolved, but better than no resolution :slight_smile:

@avocado876 I’m glad to hear that everything seems to be working now. Let us know if you run into this issue going forward.