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.
Live demo
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"