Node.js sample

Zoom API and SDK reference

Runnable Express examples for Zoom webhooks, Server-to-Server OAuth, Client Credentials OAuth, OAuth redirects, Meeting SDK signatures, token refresh, REST API calls, and token verification.

GitHub source

Live demo

WebhookInspect the last received webhook payload. S2S OAuthView the account token request shape without exposing a live token. Client CredentialsView the chatbot or Marketplace token request shape without exposing a live token. OAuth redirectView the authorization-code exchange shape without exposing a live token. Refresh tokenExchange a refresh token for a new access token. Meeting SDKGET for info, POST for a signature. REST APICall the Zoom REST API with an access token.

Code samples

Webhook handler

   
 // Define a function to handle webhook requests
function handleWebhookRequest(req, res, secretToken, path) {


  if (req.method === 'POST') {
  // Check if the event type is "endpoint.url_validation"
  if (req.body.event === 'endpoint.url_validation') {
    const hashForValidate = crypto.createHmac('sha256', secretToken)
      .update(req.body.payload.plainToken)
      .digest('hex');

    res.status(200).json({
      "plainToken": req.body.payload.plainToken,
      "encryptedToken": hashForValidate
    });
  } else {


    res.status(200).send();
  }
}else if (req.method === 'GET') {



} else {
  // Handle unsupported HTTP methods
  res.status(405).send("Method Not Allowed");
}

}



app.post('/webhook/', (req, res) => {handleWebhookRequest(req,res, process.env.ZOOM_WEBHOOK_SECRET_TOKEN,"webhook");});
app.get('/webhook/', (req, res) => {handleWebhookRequest(req,res, process.env.ZOOM_WEBHOOK_SECRET_TOKEN,"webhook");});


Server-to-Server OAuth sample

    
app.get('/s2soauth', (req, res) => {
  res.status(200).json({
    message: 'Live S2S OAuth token requests are disabled on this public demo.',
    liveTokenRequestDisabled: true,
    grantType: 'account_credentials',
    tokenUrl: 'https://zoom.us/oauth/token?grant_type=account_credentials&account_id={ACCOUNT_ID}',
    requestShape: {
      method: 'POST',
      headers: {
        Authorization: 'Basic base64({CLIENT_ID}:{CLIENT_SECRET})',
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    },
    expectedResponseFields: [
      'access_token',
      'token_type',
      'expires_in',
      'scope',
      'api_url',
    ],
  });
});
    
    

Client Credentials OAuth sample

    
	app.get(['/clientcredential', '/clientcredentials'], (req, res) => {
	  const requestedClientId = (req.query.clientid || req.query.clientId || '').trim();
	
	  res.status(200).json({
	    message: 'Live client credentials token requests are disabled on this public demo.',
	    liveTokenRequestDisabled: true,
	    reason: 'This GET endpoint intentionally does not load a client ID, client secret, or return an access token.',
	    requestedClientId: requestedClientId || undefined,
	    grantType: 'client_credentials',
	    tokenUrl: 'https://zoom.us/oauth/token?grant_type=client_credentials',
	    requestShape: {
	      method: 'POST',
	      headers: {
	        Authorization: 'Basic base64({CLIENT_ID}:{CLIENT_SECRET})',
	        'Content-Type': 'application/x-www-form-urlencoded',
	      },
	    },
	    zoomResponseSummary: 'Zoom returns bearer token metadata from a private server-side POST. This public GET route does not proxy that response.',
	  });
	});
    
  

OAuth redirect sample

    
	app.get('/redirecturlforoauth', (req, res) => {
	  const type = req.query.type || '';
	  const requestedClientId = (req.query.clientid || req.query.clientId || '').trim();
	  const hasCode = Boolean(req.query.code);
	
	  res.status(200).json({
	    message: 'Live OAuth authorization-code token exchange is disabled on this public demo.',
	    liveTokenExchangeDisabled: true,
	    reason: 'This GET endpoint intentionally does not load OAuth client credentials, read cached token files, exchange ?code= values, or return an access token.',
	    codeReceived: hasCode,
	    requestedType: type || undefined,
	    requestedClientId: requestedClientId || undefined,
	    grantType: 'authorization_code',
	    authorizeUrl: 'https://zoom.us/oauth/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}',
	    tokenExchangeShape: {
	      method: 'POST',
	      url: 'https://zoom.us/oauth/token',
	      headers: {
	        Authorization: 'Basic base64({CLIENT_ID}:{CLIENT_SECRET})',
	        'Content-Type': 'application/x-www-form-urlencoded',
	      },
	      body: {
	        grant_type: 'authorization_code',
	        code: '{AUTHORIZATION_CODE}',
	        redirect_uri: 'https://nodejs.asdc.cc/redirecturlforoauth',
	      },
	    },
	    zoomResponseSummary: 'Zoom returns bearer and refresh token metadata from a private server-side POST. This public GET route does not proxy that response.',
	  });
	});


Meeting SDK signature

    
app.post('/meeting/', (req, res) => {

  const now = Math.round((new Date().getTime()) / 1000)
  //make it valid 5 minute ago
  const iat =now -  5 * 60;
    //make it expire 30 minutes later
    const exp = now + 30 * 60;

  const oHeader = { alg: 'HS256', typ: 'JWT' }

  const oPayload = {
    appKey: process.env.ZOOM_SDK_KEY,
    mn: req.body.meetingNumber,
    role: req.body.role,
    iat: iat,
    exp: exp,
    tokenExp: exp 
  }

  const sHeader = JSON.stringify(oHeader)
  const sPayload = JSON.stringify(oPayload)
  const signature = KJUR.jws.JWS.sign('HS256', sHeader, sPayload, process.env.ZOOM_SDK_SECRET)

  res.json({
    signature: signature,
    appKey: process.env.ZOOM_SDK_KEY
  })
})

OAuth refresh token

    
app.get('/oauthrefreshtoken', async (req, res) => {
  const refreshToken = req.query.code;
  const clientId = process.env.ZOOM_OAUTH_USERLEVEL_CLIENT_ID;
  const clientSecret = process.env.ZOOM_OAUTH_USERLEVEL_CLIENT_SECRET;

  if (!refreshToken) {
    return res.status(400).json({ error: 'Missing ?code=refresh_token parameter' });
  }

  try {
    const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
    const response = await axios.post(
      'https://zoom.us/oauth/token',
      new URLSearchParams({
        refresh_token: refreshToken,
        grant_type: 'refresh_token',
      }).toString(),
      {
        headers: {
          Authorization: `Basic ${credentials}`,
          'Content-Type': 'application/x-www-form-urlencoded',
        },
      }
    );

    res.status(200).json(response.data);
  } catch (error) {
    res.status(500).json({ error: error.response?.data || error.message });
  }
});
    
    

REST API call

    
// Function to make a REST API request using the bearer token
async function makeApiRequestWithToken(bearerToken,meetingNumber) {
  try {
    const apiUrl = `https://api.zoom.us/v2/meetings/${meetingNumber}/jointoken/local_recording`;
    
    // Define your API request parameters
    const apiRequestData = {
      method: 'GET', // Change to the HTTP method you need
      url: apiUrl, // Replace with your API endpoint URL
      headers: {
        'Authorization': `Bearer ${bearerToken}`,
        // Add other headers as needed
      },
    };

    // Send the API request with the bearer token
    const response = await axios(apiRequestData);
    const recordingToken = response.data.token;
    // Return the response data
    return recordingToken ;
  } catch (error) {
    console.error('Error making API request:', error.message);
    return error.message ; // Optionally rethrow the error
  }
}

Environment variables

    

ZOOM_VIDEO_SDK_KEY="xxxxxxx"
ZOOM_VIDEO_SDK_SECRET="xxxxxxx"

#ZOOM_SDK_KEY="xxxxxxx"
#ZOOM_SDK_SECRET="xxxxxxx"


ZOOM_WEBHOOK_SECRET_TOKEN="xxxxxxx"


ZOOM_MSDKWEBHOOK_SECRET_TOKEN="xxxxxxx"
ZOOM_SDK_KEY="xxxxxxx"
ZOOM_SDK_SECRET="xxxxxxx"
ZOOM_CLIENT_ID_NEW="xxxxxxx"
ZOOM_CLIENT_SECRET_NEW="xxxxxxx"
ZOOM_CLIENT_CREDENTIALS_CLIENT_ID="xxxxxxx"
ZOOM_CLIENT_CREDENTIALS_CLIENT_SECRET="xxxxxxx"


ZOOM_VSDK_WEBHOOK_SECRET_TOKEN="xxxxxxx"

ZOOM_S2SOAUTH_WEBHOOK_SECRET_TOKEN="xxxxxxx"
ZOOM_S2S_CLIENT_ID="xxxxxxx"
ZOOM_S2S_CLIENT_SECRET="xxxxxxx"
ZOOM_S2S_ACCOUNTID="xxxxxxx"

ZOOM_OAUTH_ACCOUNTLEVEL_WEBHOOK_SECRET_TOKEN="xxxxxxx"
ZOOM_OAUTH_ACCOUNTLEVEL_CLIENT_ID="xxxxxxx"
ZOOM_OAUTH_ACCOUNTLEVEL_CLIENT_SECRET="xxxxxxx"


ZOOM_OAUTH_USERLEVEL_WEBHOOK_SECRET_TOKEN="xxxxxxx"
ZOOM_OAUTH_USERLEVEL_CLIENT_ID="xxxxxxx"
ZOOM_OAUTH_USERLEVEL_CLIENT_SECRET="xxxxxxx"