Node.js sample

Zoom API and SDK reference

Runnable Express examples for Zoom webhooks, Server-to-Server 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 OAuthRequest an account access token. OAuth redirectExchange an authorization code. 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 token

    
// Function to fetch a bearer token
async function fetchBearerToken() {
  try {
    // Create a Basic Authorization header with client credentials
    const credentials = Buffer.from(`${process.env.ZOOM_S2S_CLIENT_ID}:${process.env.ZOOM_S2S_CLIENT_SECRET}`).toString('base64');
    const apiUrl = `https://zoom.us/oauth/token?grant_type=account_credentials&account_id=${process.env.ZOOM_S2S_ACCOUNTID}`;
    
    // Define the token request parameters
    const tokenRequestData = {
      method: 'POST',
      url: apiUrl,
      headers: {
        'Authorization': `Basic ${credentials}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      
    };

    // Send the token request
    const response = await axios(tokenRequestData);

    // Extract the access token from the response
    const accessToken = response.data.access_token;
    // Return 
    return accessToken;
   
  } catch (error) {
    return error.message;
  }
}
    
    

OAuth redirect handler

    
app.get('/redirecturlforoauth', async (req, res) => {
  const code = req.query.code;
  const type = req.query.type || '';
  let clientId = process.env.ZOOM_SDK_KEY;
  let clientSecret = process.env.ZOOM_SDK_SECRET;
  const tokenFilename = type ? `/oauthtoken-${type}.txt` : '/oauthtoken.txt';

  if (type === 'admin') {
    clientId = process.env.ZOOM_OAUTH_ADMINLEVEL_CLIENT_ID;
    clientSecret = process.env.ZOOM_OAUTH_ADMINLEVEL_CLIENT_SECRET;
  }

  if (!code) {
    fs.readFile(__dirname + tokenFilename, 'utf8', (err, data) => {
      if (err) {
        return res.status(200).json({ message: 'No OAuth data. Add ?code=xxx to exchange authorization code.' });
      }
      res.status(200).json(JSON.parse(data));
    });
    return;
  }

  const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
  const redirectUri = `https://nodejs.asdc.cc/redirecturlforoauth${type ? `?type=${encodeURIComponent(type)}` : ''}`;

  const response = await axios.post(
    'https://zoom.us/oauth/token',
    new URLSearchParams({
      code,
      grant_type: 'authorization_code',
      redirect_uri: redirectUri,
    }).toString(),
    {
      headers: {
        Authorization: `Basic ${credentials}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    }
  );

  fs.writeFile(__dirname + tokenFilename, JSON.stringify(response.data, null, 2), 'utf8', () => {});
  res.status(200).json(response.data);
});


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 = {
    sdkKey: process.env.ZOOM_SDK_KEY,
    mn: req.body.meetingNumber,
    role: req.body.role,
    iat: iat,
    exp: exp,
    appKey: process.env.ZOOM_SDK_KEY,
    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,
    sdkKey: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_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"