Zoom

CommunicationVideo ConferencingAPISDKWebhookApp Development

Communication Tool

Zoom

Overview

Zoom is a communication platform specializing in video conferencing. It supports up to 1,000 participants and provides screen sharing, recording functionality, and webinar features. With high-quality audio and video quality, demand continues even after the pandemic due to the establishment of remote work practices.

Details

Zoom is a video conferencing service established in 2011, now serving as a synonymous presence for video conferencing worldwide. With the establishment of remote work after the pandemic, demand continues to grow, expanding adoption in education, healthcare, and enterprise sectors.

Zoom's key strength is its simple, user-friendly interface and stable, high-quality video and audio communication. Using Zoom SDK and Zoom API, you can integrate Zoom functionality into your own applications or build custom solutions. It provides rich developer features including event notifications via Webhooks, extension development through Apps for Zoom, and phone integration via Zoom Phone API.

In 2024, the developer ecosystem was further enhanced with AI feature improvements, Zoom Apps Marketplace expansion, Zoom Contact Center API, and new SDK features. Particularly, Meeting SDK, Video SDK, and Cloud Recording API make it easy to embed Zoom functionality into other applications.

Pros and Cons

Pros

  • High-Quality Communication: Stable audio and video quality
  • Large-Scale Support: Supports up to 1,000 participants
  • User-Friendly: Intuitive and easy-to-understand user interface
  • Rich APIs: Comprehensive SDK, REST API, and Webhook offerings
  • Recording & Broadcasting: Cloud recording and live streaming features
  • Integration Features: Connectivity with calendars, CRM, LMS, etc.
  • Security: End-to-end encryption and waiting room features
  • Developer Ecosystem: Apps for Zoom and Marketplace

Cons

  • Cost: High-feature plans are relatively expensive
  • Security Concerns: Past security issues (now improved)
  • Resource Consumption: CPU and bandwidth usage for high-quality communication
  • API Limitations: Rate limits and usage restrictions
  • Dependency: High dependency on Zoom services
  • UI Changes: Need to adapt to frequent UI updates

Key Links

Code Examples

Basic Zoom Meeting API Usage

const axios = require('axios');

class ZoomAPI {
  constructor(jwt) {
    this.jwt = jwt;
    this.baseURL = 'https://api.zoom.us/v2';
  }

  // Create meeting
  async createMeeting(userId, meetingOptions) {
    try {
      const response = await axios.post(
        `${this.baseURL}/users/${userId}/meetings`,
        {
          topic: meetingOptions.topic,
          type: 2, // Scheduled meeting
          start_time: meetingOptions.start_time,
          duration: meetingOptions.duration,
          settings: {
            host_video: true,
            participant_video: true,
            waiting_room: true,
            mute_upon_entry: true,
            approval_type: 0 // Auto-approve
          }
        },
        {
          headers: {
            'Authorization': `Bearer ${this.jwt}`,
            'Content-Type': 'application/json'
          }
        }
      );
      
      return response.data;
    } catch (error) {
      console.error('Error creating meeting:', error.response.data);
      throw error;
    }
  }

  // List meetings
  async listMeetings(userId) {
    try {
      const response = await axios.get(
        `${this.baseURL}/users/${userId}/meetings`,
        {
          headers: {
            'Authorization': `Bearer ${this.jwt}`
          }
        }
      );
      
      return response.data;
    } catch (error) {
      console.error('Error fetching meetings:', error.response.data);
      throw error;
    }
  }

  // Get meeting details
  async getMeeting(meetingId) {
    try {
      const response = await axios.get(
        `${this.baseURL}/meetings/${meetingId}`,
        {
          headers: {
            'Authorization': `Bearer ${this.jwt}`
          }
        }
      );
      
      return response.data;
    } catch (error) {
      console.error('Error fetching meeting details:', error.response.data);
      throw error;
    }
  }

  // Delete meeting
  async deleteMeeting(meetingId) {
    try {
      await axios.delete(
        `${this.baseURL}/meetings/${meetingId}`,
        {
          headers: {
            'Authorization': `Bearer ${this.jwt}`
          }
        }
      );
      
      return { success: true };
    } catch (error) {
      console.error('Error deleting meeting:', error.response.data);
      throw error;
    }
  }
}

// Usage example
const zoomAPI = new ZoomAPI(process.env.ZOOM_JWT);

async function scheduleMeeting() {
  try {
    const meeting = await zoomAPI.createMeeting('[email protected]', {
      topic: 'Weekly Team Meeting',
      start_time: '2024-01-15T10:00:00Z',
      duration: 60
    });
    
    console.log('Meeting created:', meeting.join_url);
    return meeting;
  } catch (error) {
    console.error('Failed to create meeting:', error);
  }
}

Zoom SDK Integration (Meeting SDK)

<!DOCTYPE html>
<html>
<head>
    <title>Zoom Meeting SDK Integration</title>
    <script src="https://source.zoom.us/2.16.0/lib/vendor/react.min.js"></script>
    <script src="https://source.zoom.us/2.16.0/lib/vendor/react-dom.min.js"></script>
    <script src="https://source.zoom.us/2.16.0/lib/vendor/redux.min.js"></script>
    <script src="https://source.zoom.us/2.16.0/lib/vendor/redux-thunk.min.js"></script>
    <script src="https://source.zoom.us/2.16.0/lib/vendor/lodash.min.js"></script>
    <script src="https://source.zoom.us/zoom-meeting-2.16.0.min.js"></script>
</head>
<body>
    <div id="meetingSDKElement"></div>
    
    <script>
        var authEndpoint = 'https://your-server.com/api/zoom/signature';
        var sdkKey = 'your-sdk-key';
        var meetingNumber = 123456789;
        var passWord = 'meeting-password';
        var role = 0; // 0: Participant, 1: Host
        var userName = 'User Name';
        var userEmail = '[email protected]';
        var registrantToken = '';
        var zakToken = '';

        document.getElementById('meetingSDKElement').style.display = 'block';

        ZoomMtg.setZoomJSLib('https://source.zoom.us/2.16.0/lib', '/av');

        ZoomMtg.preLoadWasm();
        ZoomMtg.prepareWebSDK();

        // Get JWT signature from remote
        function getSignature() {
            fetch(authEndpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    meetingNumber: meetingNumber,
                    role: role
                })
            }).then((response) => {
                return response.json();
            }).then((data) => {
                console.log(data);
                startMeeting(data.signature);
            }).catch((error) => {
                console.error(error);
            });
        }

        function startMeeting(signature) {
            ZoomMtg.init({
                leaveUrl: 'https://your-website.com',
                success: (success) => {
                    console.log(success);

                    ZoomMtg.join({
                        signature: signature,
                        meetingNumber: meetingNumber,
                        userName: userName,
                        sdkKey: sdkKey,
                        userEmail: userEmail,
                        passWord: passWord,
                        tk: registrantToken,
                        zak: zakToken,
                        success: (success) => {
                            console.log('Join meeting success');
                        },
                        error: (error) => {
                            console.log(error);
                        }
                    });
                },
                error: (error) => {
                    console.log(error);
                }
            });
        }

        getSignature();
    </script>
</body>
</html>

Webhook Processing

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

// Webhook signature verification
function verifyWebhook(payload, headers) {
  const message = `v0:${headers['x-zm-request-timestamp']}:${payload}`;
  const hashForVerify = crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET)
    .update(message)
    .digest('hex');
  const signature = `v0=${hashForVerify}`;
  
  return headers['x-zm-signature'] === signature;
}

// Webhook endpoint
app.post('/zoom/webhook', (req, res) => {
  const payload = JSON.stringify(req.body);
  
  // Signature verification
  if (!verifyWebhook(payload, req.headers)) {
    return res.status(401).send('Unauthorized');
  }

  const { event, payload: eventPayload } = req.body;

  switch (event) {
    case 'meeting.started':
      console.log('Meeting started:', eventPayload.object);
      handleMeetingStarted(eventPayload.object);
      break;
      
    case 'meeting.ended':
      console.log('Meeting ended:', eventPayload.object);
      handleMeetingEnded(eventPayload.object);
      break;
      
    case 'meeting.participant_joined':
      console.log('Participant joined:', eventPayload.object);
      handleParticipantJoined(eventPayload.object);
      break;
      
    case 'meeting.participant_left':
      console.log('Participant left:', eventPayload.object);
      handleParticipantLeft(eventPayload.object);
      break;
      
    case 'recording.completed':
      console.log('Recording completed:', eventPayload.object);
      handleRecordingCompleted(eventPayload.object);
      break;
      
    default:
      console.log('Unhandled event:', event);
  }

  res.status(200).send('OK');
});

// Event handlers
async function handleMeetingStarted(meeting) {
  // Meeting start processing
  console.log(`Meeting "${meeting.topic}" started at ${meeting.start_time}`);
  
  // Slack notification example
  await sendSlackNotification({
    text: `🚀 Meeting "${meeting.topic}" has started!`,
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*Meeting Started*\n*Topic:* ${meeting.topic}\n*ID:* ${meeting.id}\n*Host:* ${meeting.host_email}`
        }
      }
    ]
  });
}

async function handleMeetingEnded(meeting) {
  // Meeting end processing
  console.log(`Meeting "${meeting.topic}" ended at ${meeting.end_time}`);
  
  const duration = new Date(meeting.end_time) - new Date(meeting.start_time);
  const minutes = Math.floor(duration / 60000);
  
  await sendSlackNotification({
    text: `✅ Meeting "${meeting.topic}" has ended (${minutes} minutes)`,
  });
}

async function handleRecordingCompleted(recording) {
  // Recording completion processing
  console.log('Recording files:', recording.recording_files);
  
  for (const file of recording.recording_files) {
    if (file.file_type === 'MP4') {
      console.log(`Video recording available: ${file.download_url}`);
      
      // Download and upload recording file
      await processRecording(file);
    }
  }
}

async function processRecording(recordingFile) {
  // Recording file processing
  const response = await fetch(recordingFile.download_url, {
    headers: {
      'Authorization': `Bearer ${process.env.ZOOM_JWT}`
    }
  });
  
  if (response.ok) {
    // Upload file to external storage (S3, etc.)
    console.log('Recording processed successfully');
  }
}

app.listen(3000, () => {
  console.log('Zoom webhook server running on port 3000');
});

Apps for Zoom Development

// Apps for Zoom application example
const express = require('express');
const app = express();

app.use(express.static('public'));
app.use(express.json());

// Zoom Apps authentication
app.get('/authorize', (req, res) => {
  const { code, state } = req.query;
  
  if (code) {
    // Get access token
    exchangeCodeForToken(code)
      .then(tokenData => {
        // Save token
        res.redirect('/app.html');
      })
      .catch(error => {
        console.error('Authorization error:', error);
        res.status(500).send('Authorization failed');
      });
  } else {
    res.status(400).send('Authorization code not provided');
  }
});

// Zoom Apps API endpoint
app.post('/api/meeting-info', async (req, res) => {
  try {
    // Get current meeting information
    const meetingContext = await zoomSdk.getMeetingContext();
    
    res.json({
      meetingId: meetingContext.meetingId,
      participants: meetingContext.participants,
      duration: meetingContext.duration
    });
  } catch (error) {
    console.error('Error getting meeting info:', error);
    res.status(500).json({ error: 'Failed to get meeting info' });
  }
});

// Zoom Apps client-side (public/app.html)
const zoomAppScript = `
<script src="https://appssdk.zoom.us/sdk.min.js"></script>
<script>
  // Zoom Apps SDK initialization
  zoomSdk.config({
    capabilities: ['getRunningContext', 'getMeetingContext'],
    version: '0.16.0'
  }).then(() => {
    console.log('Zoom Apps SDK initialized');
    loadMeetingData();
  }).catch((error) => {
    console.error('SDK initialization failed', error);
  });

  async function loadMeetingData() {
    try {
      // Get running context
      const runningContext = await zoomSdk.getRunningContext();
      console.log('Running context:', runningContext);

      // Get meeting context
      const meetingContext = await zoomSdk.getMeetingContext();
      console.log('Meeting context:', meetingContext);

      // Update UI
      document.getElementById('meetingId').textContent = meetingContext.meetingId;
      document.getElementById('participants').textContent = meetingContext.participants?.length || 0;
      
    } catch (error) {
      console.error('Error loading meeting data:', error);
    }
  }

  // Update participant list
  async function updateParticipants() {
    try {
      const participants = await zoomSdk.getMeetingParticipants();
      const participantsList = document.getElementById('participantsList');
      
      participantsList.innerHTML = participants
        .map(p => \`<li>\${p.displayName} - \${p.role}</li>\`)
        .join('');
        
    } catch (error) {
      console.error('Error updating participants:', error);
    }
  }

  // Set event listeners
  zoomSdk.addEventListener('onParticipantChange', (event) => {
    console.log('Participant change:', event);
    updateParticipants();
  });

  zoomSdk.addEventListener('onMeetingConfigChanged', (event) => {
    console.log('Meeting config changed:', event);
  });
</script>
`;

Cloud Recording API

class ZoomRecordingAPI {
  constructor(jwt) {
    this.jwt = jwt;
    this.baseURL = 'https://api.zoom.us/v2';
  }

  // Get recordings list
  async getRecordings(userId, from, to) {
    try {
      const response = await axios.get(
        `${this.baseURL}/users/${userId}/recordings`,
        {
          params: {
            from,
            to,
            page_size: 300
          },
          headers: {
            'Authorization': `Bearer ${this.jwt}`
          }
        }
      );
      
      return response.data;
    } catch (error) {
      console.error('Error fetching recordings:', error.response.data);
      throw error;
    }
  }

  // Get recording details
  async getRecording(meetingId) {
    try {
      const response = await axios.get(
        `${this.baseURL}/meetings/${meetingId}/recordings`,
        {
          headers: {
            'Authorization': `Bearer ${this.jwt}`
          }
        }
      );
      
      return response.data;
    } catch (error) {
      console.error('Error fetching recording details:', error.response.data);
      throw error;
    }
  }

  // Download recording
  async downloadRecording(downloadUrl, outputPath) {
    try {
      const response = await axios({
        method: 'GET',
        url: downloadUrl,
        responseType: 'stream',
        headers: {
          'Authorization': `Bearer ${this.jwt}`
        }
      });

      const writer = fs.createWriteStream(outputPath);
      response.data.pipe(writer);

      return new Promise((resolve, reject) => {
        writer.on('finish', resolve);
        writer.on('error', reject);
      });
    } catch (error) {
      console.error('Error downloading recording:', error);
      throw error;
    }
  }

  // Delete recording
  async deleteRecording(meetingId) {
    try {
      await axios.delete(
        `${this.baseURL}/meetings/${meetingId}/recordings`,
        {
          headers: {
            'Authorization': `Bearer ${this.jwt}`
          }
        }
      );
      
      return { success: true };
    } catch (error) {
      console.error('Error deleting recording:', error.response.data);
      throw error;
    }
  }
}

// Usage example
const recordingAPI = new ZoomRecordingAPI(process.env.ZOOM_JWT);

async function processRecentRecordings() {
  const fromDate = '2024-01-01';
  const toDate = '2024-01-31';
  
  try {
    const recordings = await recordingAPI.getRecordings('[email protected]', fromDate, toDate);
    
    for (const meeting of recordings.meetings) {
      console.log(`Processing recordings for meeting: ${meeting.topic}`);
      
      for (const file of meeting.recording_files) {
        if (file.file_type === 'MP4') {
          const outputPath = `./recordings/${meeting.id}_${file.id}.mp4`;
          await recordingAPI.downloadRecording(file.download_url, outputPath);
          console.log(`Downloaded: ${outputPath}`);
        }
      }
    }
  } catch (error) {
    console.error('Failed to process recordings:', error);
  }
}

Environment Variables Configuration

# .env file
ZOOM_JWT=your-zoom-jwt-token
ZOOM_API_KEY=your-zoom-api-key
ZOOM_API_SECRET=your-zoom-api-secret
ZOOM_WEBHOOK_SECRET=your-webhook-secret-token

# SDK settings
ZOOM_SDK_KEY=your-sdk-key
ZOOM_SDK_SECRET=your-sdk-secret

# Zoom Apps settings
ZOOM_APP_CLIENT_ID=your-app-client-id
ZOOM_APP_CLIENT_SECRET=your-app-client-secret
ZOOM_APP_REDIRECT_URL=https://your-app.com/authorize

# External integrations
SLACK_WEBHOOK_URL=your-slack-webhook-url
AWS_S3_BUCKET=your-s3-bucket-name

Docker Configuration Example

FROM node:18-alpine

WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm ci --only=production

# Copy application
COPY . .

# Environment variables
ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# Run application
CMD ["npm", "start"]