Build Zoom Rest API App
OpenAIOfficialCreated by ZoomDistributed by OpenAI
- Skill
- Tool integration
- Software development
- MIT
- Medium risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to interacting with the Zoom REST API. It offers guidance on selecting appropriate authentication flows, managing resources such as meetings and users, and implementing robust API interaction patterns like pagination and rate-limit handling. It includes reference documentation for various Zoom product areas, including account management and AI Companion services, to assist developers in building API-integrated applications.
What it does
- Provides endpoint inventories for Zoom API product areas.
- Outlines authentication requirements for user-level, account-level, and server-to-server workflows.
- Defines patterns for implementing API wrappers with pagination and retry logic.
- Offers guidance on webhook processing, including signature verification.
- Assists in troubleshooting common API issues like scope mismatches and rate limits.
When to use it
- When developing applications that need to programmatically manage Zoom meetings, users, or recordings.
- When integrating Zoom account settings or dashboard metrics into custom software.
- When setting up event-driven workflows using Zoom webhooks.
- When debugging authentication or scope-related errors during Zoom API integration.
Advantages
- Centralizes access to official Zoom API endpoint documentation.
- Standardizes the implementation of API interaction patterns.
- Clarifies the selection of correct authentication scopes for different API operations.
- Provides a clear methodology for handling API rate limits and resource management.
Usage examples
- “How do I list meeting participants using the Zoom API?”
- The agent provides the correct endpoint path from the dashboard references and explains the necessary OAuth scopes and pagination requirements.
- “What authentication should I use for an account-level Zoom integration?”
- The agent guides you through the account-level OAuth flow requirements as defined in the authentication documentation.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/openai/build-zoom-rest-api-app/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip build-zoom-rest-api-app.zip -d .agents/skills
Documentation by Zoom
Build Zoom REST API App
Use this skill when the task needs deterministic Zoom API calls or resource management from application code.
Workflow
Define the resource and actor: user, meeting, webinar, recording, docs, chat, phone, account, or admin-level workflow.
Select the endpoint and required scopes from the reference files before coding.
Confirm auth fit: user-level OAuth for user-owned resources, account-level OAuth for admin workflows, and only use server-to-server OAuth where the target API documents support for it.
Implement narrow API wrappers with explicit pagination, retry, idempotency, and rate-limit handling.
Treat webhook processing as a separate event-ingestion path with signature verification and replay protection.
Debug by checking token audience, missing scopes, resource ownership, account settings, API enablement, and rate-limit headers.
References
Full preserved guide: references/full-guide.md
API architecture: concepts/api-architecture.md
Authentication flows: concepts/authentication-flows.md
Rate limits: references/rate-limits.md
Meetings: references/meetings.md
Recordings: references/recordings.md
Users: references/users.md
Token and scope playbook: troubleshooting/token-scope-playbook.md
Files
- ATTRIBUTION.mdAdded by AutoGineer878 B
- LICENSELicence file added by AutoGineer1.1 KiB
- RUNBOOK.md3.1 KiB
- SKILL.md1.6 KiB
- agents/openai.yaml143 B
- concepts/api-architecture.md8.9 KiB
- concepts/authentication-flows.md9.1 KiB
- concepts/meeting-urls-and-sdk-joining.md1.2 KiB
- concepts/rate-limiting-strategy.md9.6 KiB
- examples/graphql-queries.md465 B
- examples/meeting-lifecycle.md14.6 KiB
- examples/recording-pipeline.md661 B
- examples/user-management.md509 B
- examples/webhook-server.md14.4 KiB
- references/accounts.md7.5 KiB
- references/ai-companion.md1.2 KiB
- references/ai-services.md1.5 KiB
- references/authentication.md11.0 KiB
- references/auto-dialer.md2.6 KiB
- references/calendar.md4.9 KiB
- references/chatbot.md1.4 KiB
- references/clips.md2.9 KiB
- references/cobrowse-sdk-api.md1.4 KiB
- references/commerce.md5.8 KiB
- references/contact-center.md33.1 KiB
- references/crc.md3.5 KiB
- references/environment-variables.md976 B
- references/events.md13.0 KiB
- references/full-guide.md25.5 KiB
- references/graphql.md8.9 KiB
- references/healthcare.md1.3 KiB
- references/mail.md6.9 KiB
- references/marketplace-apps.md4.0 KiB
- references/meetings.md21.3 KiB
- references/number-management.md4.8 KiB
- references/openapi.md4.4 KiB
- references/phone.md45.9 KiB
- references/qss.md1.6 KiB
- references/quality-management.md1.7 KiB
- references/rate-limits.md9.9 KiB
- references/recordings.md1.4 KiB
- references/reports.md1.2 KiB
- references/revenue-accelerator.md9.4 KiB
- references/rooms.md15.3 KiB
- references/scheduler.md3.8 KiB
- references/scim2.md1.9 KiB
- references/tasks.md2.6 KiB
- references/team-chat.md13.5 KiB
- references/users.md7.3 KiB
- references/video-management.md4.0 KiB
- references/video-sdk-api.md5.3 KiB
- references/virtual-agent.md2.2 KiB
- references/webinars.md1.2 KiB
- references/whiteboard.md5.3 KiB
- references/workforce-management.md3.1 KiB
- references/zoom-docs.md3.2 KiB
- troubleshooting/common-errors.md11.6 KiB
- troubleshooting/common-issues.md650 B
- troubleshooting/forum-top-questions.md1.7 KiB
- troubleshooting/token-scope-playbook.md1.5 KiB
examples/webhook-server.md
Webhook Server - Express.js with CRC Validation and Signature Verification
Production-ready webhook server implementation for receiving Zoom webhook events with CRC (Challenge-Response Check) validation and HMAC signature verification.
For comprehensive webhook documentation, see the webhooks skill.
Quick Start
1. Install Dependencies
npm install express body-parser crypto2. Basic Webhook Server
const express = require('express');
const crypto = require('crypto');
const app = express();
// Zoom webhook secret token (from your app's Feature page)
const WEBHOOK_SECRET_TOKEN = process.env.ZOOM_WEBHOOK_SECRET;
// Parse JSON bodies
app.use(express.json());
// Webhook endpoint
app.post('/webhook', (req, res) => {
const { event, payload } = req.body;
// Handle CRC validation (Challenge-Response Check)
if (event === 'endpoint.url_validation') {
return handleCRC(req, res);
}
// Verify signature
if (!verifySignature(req)) {
console.error('Invalid signature');
return res.status(401).send('Unauthorized');
}
// Handle events
handleEvent(event, payload);
// Always respond with 200 within 3 seconds
res.status(200).send();
});
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});CRC (Challenge-Response Check) Validation
When you add a webhook URL or make changes, Zoom sends a validation request. You must respond within 3 seconds.
CRC Flow
Zoom sends POST with
event: "endpoint.url_validation"Your server hashes the
plainTokenusing your webhook secretRespond with JSON containing both
plainTokenandencryptedToken
Implementation
function handleCRC(req, res) {
const { plainToken } = req.body.payload;
// Hash the plainToken with HMAC-SHA256
const encryptedToken = crypto
.createHmac('sha256', WEBHOOK_SECRET_TOKEN)
.update(plainToken)
.digest('hex');
// Respond within 3 seconds
res.status(200).json({
plainToken,
encryptedToken
});
console.log('CRC validation successful');
}CRC Request Example
{
"event": "endpoint.url_validation",
"payload": {
"plainToken": "qgg8vlvZRS6UYooatFL8Aw"
},
"event_ts": 1654503849680
}CRC Response Example
{
"plainToken": "qgg8vlvZRS6UYooatFL8Aw",
"encryptedToken": "23a89b634c017e5364a1c8d9c8ea909b60dd5599e2bb04bb1558d9c3a121faa5"
}Signature Verification
Verify that webhook requests actually come from Zoom by checking the HMAC signature.
Signature Verification Flow
Extract
x-zm-signatureandx-zm-request-timestampheadersConstruct message:
v0:{timestamp}:{body}Hash message with HMAC-SHA256 using your webhook secret
Prepend
v0=to the hashCompare with
x-zm-signatureheader
Implementation
function verifySignature(req) {
const signature = req.headers['x-zm-signature'];
const timestamp = req.headers['x-zm-request-timestamp'];
if (!signature || !timestamp) {
console.error('Missing signature headers');
return false;
}
// Construct the message
const message = `v0:${timestamp}:${JSON.stringify(req.body)}`;
// Hash the message
const hashForVerify = crypto
.createHmac('sha256', WEBHOOK_SECRET_TOKEN)
.update(message)
.digest('hex');
// Prepend v0=
const computedSignature = `v0=${hashForVerify}`;
// Compare signatures
return signature === computedSignature;
}Signature Headers Example
POST /webhook HTTP/1.1
Host: example.com
x-zm-signature: v0=a05d830fa017433bc47887f835a00b9ff33d3882f22f63a2986a8es270341
x-zm-request-timestamp: 1658940994
Content-Type: application/json
{"event":"meeting.started","payload":{...}}Event Handling
Event Router
function handleEvent(event, payload) {
switch (event) {
case 'meeting.created':
handleMeetingCreated(payload);
break;
case 'meeting.started':
handleMeetingStarted(payload);
break;
case 'meeting.ended':
handleMeetingEnded(payload);
break;
case 'meeting.participant_joined':
handleParticipantJoined(payload);
break;
case 'recording.completed':
handleRecordingCompleted(payload);
break;
default:
console.log(`Unhandled event: ${event}`);
}
}Event Handlers
function handleMeetingStarted(payload) {
const { id, uuid, topic, start_time } = payload.object;
console.log(`Meeting started: ${topic} (ID: ${id})`);
// Your logic: Send notifications, start recording, etc.
// Example: Trigger auto-recording
// await startCloudRecording(id);
}
function handleMeetingEnded(payload) {
const { id, uuid, topic, duration } = payload.object;
console.log(`Meeting ended: ${topic} (Duration: ${duration}min)`);
// Your logic: Process analytics, trigger workflows, etc.
}
function handleRecordingCompleted(payload) {
const { id, uuid, topic, recording_files } = payload.object;
console.log(`Recording ready: ${topic}`);
// Download recordings (see recording-pipeline.md)
recording_files.forEach(file => {
console.log(`- ${file.file_type}: ${file.download_url}`);
// downloadRecording(file.download_url, file.id);
});
}
function handleParticipantJoined(payload) {
const { participant } = payload.object;
console.log(`Participant joined: ${participant.user_name}`);
// Your logic: Track attendance, send welcome message, etc.
}Complete Production Server
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET_TOKEN = process.env.ZOOM_WEBHOOK_SECRET;
const PORT = process.env.PORT || 3000;
// Middleware
app.use(express.json());
// Request logging
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
});
// Webhook endpoint
app.post('/webhook', async (req, res) => {
try {
const { event, payload, event_ts } = req.body;
// CRC validation
if (event === 'endpoint.url_validation') {
return handleCRC(req, res);
}
// Verify signature
if (!verifySignature(req)) {
console.error('Signature verification failed');
return res.status(401).send('Unauthorized');
}
// Log event
console.log(`Event received: ${event} at ${new Date(event_ts)}`);
// Handle event asynchronously
setImmediate(() => {
handleEvent(event, payload).catch(error => {
console.error('Error handling event:', error);
});
});
// Respond immediately (within 3 seconds)
res.status(200).send();
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Internal Server Error');
}
});
// CRC validation
function handleCRC(req, res) {
const { plainToken } = req.body.payload;
const encryptedToken = crypto
.createHmac('sha256', WEBHOOK_SECRET_TOKEN)
.update(plainToken)
.digest('hex');
res.status(200).json({ plainToken, encryptedToken });
console.log('CRC validation successful');
}
// Signature verification
function verifySignature(req) {
const signature = req.headers['x-zm-signature'];
const timestamp = req.headers['x-zm-request-timestamp'];
if (!signature || !timestamp) {
return false;
}
const message = `v0:${timestamp}:${JSON.stringify(req.body)}`;
const hashForVerify = crypto
.createHmac('sha256', WEBHOOK_SECRET_TOKEN)
.update(message)
.digest('hex');
const computedSignature = `v0=${hashForVerify}`;
return signature === computedSignature;
}
// Event handler
async function handleEvent(event, payload) {
switch (event) {
case 'meeting.started':
await handleMeetingStarted(payload);
break;
case 'meeting.ended':
await handleMeetingEnded(payload);
break;
case 'recording.completed':
await handleRecordingCompleted(payload);
break;
// Add more event handlers as needed
default:
console.log(`Unhandled event: ${event}`);
}
}
async function handleMeetingStarted(payload) {
console.log(`Meeting started: ${payload.object.topic}`);
// Your business logic
}
async function handleMeetingEnded(payload) {
console.log(`Meeting ended: ${payload.object.topic}`);
// Your business logic
}
async function handleRecordingCompleted(payload) {
console.log(`Recording completed: ${payload.object.topic}`);
// Download logic (see recording-pipeline.md)
}
// Health check
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Start server
app.listen(PORT, () => {
console.log(`Webhook server running on port ${PORT}`);
console.log(`Webhook endpoint: ${process.env.PUBLIC_BASE_URL || 'https://YOUR_PUBLIC_BASE_URL'}/webhook`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
process.exit(0);
});Webhook Retry Policy
Zoom automatically retries failed webhooks 3 times with exponential backoff:
First retry: 5 minutes after initial failure
Second retry: 20 minutes after first retry
Third retry: 60 minutes after second retry
Retry Conditions
Zoom retries for:
HTTP status codes ≥ 500
Network errors (connection refused, timeout, etc.)
Zoom does NOT retry for:
HTTP status codes 200-299 (success)
HTTP status codes 300-399 (redirects)
HTTP status codes 400-499 (client errors)
Handling Retries
// Track processed events to avoid duplicate processing
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
const { event, event_ts, payload } = req.body;
// Create unique event ID
const eventId = `${event}-${event_ts}-${payload.object?.id || ''}`;
// Check if already processed (duplicate due to retry)
if (processedEvents.has(eventId)) {
console.log(`Duplicate event: ${eventId}`);
return res.status(200).send(); // Still return 200
}
// Mark as processed
processedEvents.add(eventId);
// Handle event
handleEvent(event, payload);
res.status(200).send();
// Clean up old entries after 2 hours
setTimeout(() => processedEvents.delete(eventId), 2 * 60 * 60 * 1000);
});Webhook Revalidation
Zoom automatically revalidates webhook endpoints every 72 hours. If revalidation fails 6 consecutive times, Zoom disables the webhook.
Revalidation Notifications
2 failures: First email notification
4 failures: Second email notification
6 failures: Webhook disabled
Ensure Uptime
// Health check with monitoring
app.get('/health', (req, res) => {
// Check dependencies (database, external APIs, etc.)
const isHealthy = checkDependencies();
if (isHealthy) {
res.status(200).json({
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
} else {
res.status(503).json({
status: 'unhealthy',
timestamp: new Date().toISOString()
});
}
});
function checkDependencies() {
// Check database connection, external APIs, etc.
return true;
}Environment Variables
# .env
ZOOM_WEBHOOK_SECRET=your_webhook_secret_token_here
PORT=3000
NODE_ENV=productionLoading Environment Variables
require('dotenv').config();
const WEBHOOK_SECRET_TOKEN = process.env.ZOOM_WEBHOOK_SECRET;
if (!WEBHOOK_SECRET_TOKEN) {
throw new Error('ZOOM_WEBHOOK_SECRET environment variable is required');
}Deployment
Requirements
HTTPS required - Zoom only sends to HTTPS endpoints
Public URL - Endpoint must be publicly accessible
TLS 1.2+ - Valid certificate from a Certificate Authority (CA)
FQDN - Fully qualified domain name (not IP address)
Response time - Respond within 3 seconds
Deployment Options
Heroku:
git push heroku mainAWS Lambda: Use API Gateway + Lambda function
Vercel/Netlify: Serverless functions
Self-hosted: Nginx + Node.js + Let's Encrypt
ngrok for Local Development
# Install ngrok
npm install -g ngrok
# Start your server
node server.js
# In another terminal, expose to public URL
ngrok http 3000
# Use the HTTPS URL in Zoom webhook configuration
# Example: https://abc123.ngrok.io/webhookTesting
Test CRC Validation
WEBHOOK_BASE_URL="http://YOUR_DEV_HOST:3000"
curl -X POST "$WEBHOOK_BASE_URL/webhook" \
-H "Content-Type: application/json" \
-d '{
"event": "endpoint.url_validation",
"payload": {
"plainToken": "test_token_123"
},
"event_ts": 1654503849680
}'Expected response:
{
"plainToken": "test_token_123",
"encryptedToken": "..."
}Test Event Handling
# Generate valid signature
TIMESTAMP=$(date +%s)
MESSAGE="v0:${TIMESTAMP}:{\"event\":\"meeting.started\",\"payload\":{\"object\":{\"id\":\"123\",\"topic\":\"Test\"}}}"
SIGNATURE="v0=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "YOUR_SECRET" -binary | xxd -p)"
WEBHOOK_BASE_URL="http://YOUR_DEV_HOST:3000"
curl -X POST "$WEBHOOK_BASE_URL/webhook" \
-H "Content-Type: application/json" \
-H "x-zm-signature: $SIGNATURE" \
-H "x-zm-request-timestamp: $TIMESTAMP" \
-d '{"event":"meeting.started","payload":{"object":{"id":"123","topic":"Test Meeting"}}}'Common Event Types
| Event | Description |
|---|---|
meeting.created | Meeting created |
meeting.updated | Meeting details changed |
meeting.deleted | Meeting deleted |
meeting.started | Meeting begins |
meeting.ended | Meeting ends |
meeting.participant_joined | Participant joins |
meeting.participant_left | Participant leaves |
recording.completed | Cloud recording ready |
recording.transcript_completed | Transcript ready |
user.created | User created |
user.updated | User updated |
user.deleted | User deleted |
See complete event catalog: webhooks skill
Related Documentation
webhooks skill - Comprehensive webhook documentation
Recording Pipeline - Download recordings from webhook events
Meeting Lifecycle - Create/update/delete meetings
Common Issues - Webhook troubleshooting
Resources
Requirements and compatibility
Written for: OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
What our scanner noticed· Medium risk
- Possible data-exfiltration endpoints
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Zoom
- Publisher
- OpenAI (Official)
- Distributed by
- OpenAI
- Source
- openai/plugins · plugins/zoom/skills/rest-api · commit d420d13
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from plugins/zoom/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Zoom or OpenAI.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download build-zoom-rest-api-app.zip