Files
2026-07-13 12:20:06 +08:00

15 KiB

Meeting Lifecycle - Complete CRUD with Webhook Integration

Complete working examples for the full meeting lifecycle: Create → Update → Start → End → Delete, with webhook event integration.

Prerequisites

  • Server-to-Server OAuth token (see Authentication Flows)
  • Scopes: meeting:write, meeting:read (minimum)
  • Base URL: https://api.zoom.us/v2

Step 1: Create a Meeting

Instant Meeting (No Fixed Time)

curl -X POST "https://api.zoom.us/v2/users/me/meetings" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "Quick Team Sync",
    "type": 1,
    "settings": {
      "join_before_host": false,
      "waiting_room": true,
      "approval_type": 2
    }
  }'

Scheduled Meeting

curl -X POST "https://api.zoom.us/v2/users/me/meetings" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type": application/json" \
  -d '{
    "topic": "Q1 Planning Meeting",
    "type": 2,
    "start_time": "2025-03-15T10:00:00Z",
    "duration": 60,
    "timezone": "America/New_York",
    "agenda": "Discuss Q1 goals and milestones",
    "settings": {
      "host_video": true,
      "participant_video": false,
      "join_before_host": false,
      "waiting_room": true,
      "mute_upon_entry": true,
      "approval_type": 2,
      "auto_recording": "cloud",
      "alternative_hosts": "alt.host@example.com"
    }
  }'

Node.js Example

async function createMeeting(accessToken, meetingData) {
  const response = await fetch('https://api.zoom.us/v2/users/me/meetings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(meetingData)
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to create meeting: ${error.message}`);
  }

  return await response.json();
}

// Usage
const meetingData = {
  topic: 'Team Standup',
  type: 2,  // Scheduled
  start_time: '2025-03-15T14:00:00Z',
  duration: 30,
  settings: {
    join_before_host: false,
    waiting_room: true
  }
};

const meeting = await createMeeting(accessToken, meetingData);
console.log('Meeting created:', meeting.id);
console.log('Join URL:', meeting.join_url);

Response

{
  "id": 93123456789,
  "uuid": "xyzAbC1234==",
  "host_id": "abc123def456",
  "topic": "Q1 Planning Meeting",
  "type": 2,
  "start_time": "2025-03-15T10:00:00Z",
  "duration": 60,
  "timezone": "America/New_York",
  "created_at": "2025-02-09T12:30:00Z",
  "join_url": "https://zoom.us/j/93123456789",
  "start_url": "https://zoom.us/s/93123456789?zak=...",
  "settings": {
    "host_video": true,
    "participant_video": false,
    "waiting_room": true,
    "auto_recording": "cloud"
  }
}

Meeting Types

Type Value Description
Instant 1 Start immediately, no fixed time
Scheduled 2 Fixed date/time
Recurring (no fixed time) 3 PMI meetings
Recurring (fixed time) 8 Series with schedule

Step 2: Update a Meeting

Update Meeting Details

curl -X PATCH "https://api.zoom.us/v2/meetings/93123456789" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "Q1 Planning - Updated",
    "start_time": "2025-03-15T14:00:00Z",
    "duration": 90,
    "settings": {
      "waiting_room": false
    }
  }'

Node.js Example

async function updateMeeting(accessToken, meetingId, updates) {
  const response = await fetch(`https://api.zoom.us/v2/meetings/${meetingId}`, {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(updates)
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to update meeting: ${error.message}`);
  }

  // PATCH returns 204 No Content on success
  return response.status === 204;
}

// Usage
const updates = {
  topic: 'Q1 Planning - Updated Agenda',
  duration: 90
};

await updateMeeting(accessToken, 93123456789, updates);
console.log('Meeting updated successfully');

Partial Updates

You only need to include fields you want to change:

// Only update topic
await updateMeeting(accessToken, meetingId, {
  topic: 'New Topic'
});

// Only update settings
await updateMeeting(accessToken, meetingId, {
  settings: {
    waiting_room: true,
    mute_upon_entry: true
  }
});

Step 3: Get Meeting Details

curl "https://api.zoom.us/v2/meetings/93123456789" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Node.js Example

async function getMeeting(accessToken, meetingId) {
  const response = await fetch(
    `https://api.zoom.us/v2/meetings/${meetingId}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to get meeting: ${error.message}`);
  }

  return await response.json();
}

// Usage
const meeting = await getMeeting(accessToken, 93123456789);
console.log('Meeting:', meeting.topic);
console.log('Start time:', meeting.start_time);
console.log('Join URL:', meeting.join_url);

Step 4: List User's Meetings

curl "https://api.zoom.us/v2/users/me/meetings?type=scheduled&page_size=30" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Node.js with Pagination

async function listAllMeetings(accessToken, userId = 'me') {
  let allMeetings = [];
  let nextPageToken = '';

  while (true) {
    const params = new URLSearchParams({
      type: 'scheduled',
      page_size: 300,
      ...(nextPageToken && { next_page_token: nextPageToken })
    });

    const response = await fetch(
      `https://api.zoom.us/v2/users/${userId}/meetings?${params}`,
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`
        }
      }
    );

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Failed to list meetings: ${error.message}`);
    }

    const data = await response.json();
    allMeetings = allMeetings.concat(data.meetings);

    nextPageToken = data.next_page_token;
    if (!nextPageToken) break;
  }

  return allMeetings;
}

// Usage
const meetings = await listAllMeetings(accessToken);
console.log(`Found ${meetings.length} meetings`);
meetings.forEach(m => console.log(`- ${m.topic} (${m.start_time})`));

Meeting List Types

Type Value Description
Scheduled scheduled Future meetings
Live live Currently active
Upcoming upcoming Within next 30 days

Step 5: Delete a Meeting

curl -X DELETE "https://api.zoom.us/v2/meetings/93123456789" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Node.js Example

async function deleteMeeting(accessToken, meetingId, options = {}) {
  const params = new URLSearchParams();
  
  // Optional: Cancel single occurrence of recurring meeting
  if (options.occurrenceId) {
    params.append('occurrence_id', options.occurrenceId);
  }
  
  // Optional: Send cancellation email
  if (options.scheduleForReminder !== undefined) {
    params.append('schedule_for_reminder', options.scheduleForReminder);
  }

  const url = `https://api.zoom.us/v2/meetings/${meetingId}${params.toString() ? '?' + params.toString() : ''}`;

  const response = await fetch(url, {
    method: 'DELETE',
    headers: {
      'Authorization': `Bearer ${accessToken}`
    }
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to delete meeting: ${error.message}`);
  }

  // DELETE returns 204 No Content on success
  return response.status === 204;
}

// Usage
await deleteMeeting(accessToken, 93123456789);
console.log('Meeting deleted successfully');

Webhook Integration

To receive real-time events for meeting lifecycle, set up webhooks. See Webhook Server Example for full implementation.

Key Meeting Events

Event When It Fires
meeting.created Meeting is created
meeting.updated Meeting details changed
meeting.deleted Meeting is deleted
meeting.started Meeting begins
meeting.ended Meeting ends
meeting.participant_joined Participant joins
meeting.participant_left Participant leaves
recording.completed Cloud recording finishes processing

Webhook Payload Example

Event: meeting.started

{
  "event": "meeting.started",
  "event_ts": 1707486720000,
  "payload": {
    "account_id": "abc123",
    "object": {
      "id": "93123456789",
      "uuid": "xyzAbC1234==",
      "host_id": "def456",
      "topic": "Q1 Planning Meeting",
      "type": 2,
      "start_time": "2025-03-15T14:00:00Z",
      "duration": 60,
      "timezone": "America/New_York"
    }
  }
}

Webhook Handler Example

// Express.js webhook endpoint
app.post('/webhook', express.json(), (req, res) => {
  const { event, payload } = req.body;

  switch (event) {
    case 'meeting.started':
      console.log(`Meeting started: ${payload.object.topic}`);
      // Trigger recording, send notifications, etc.
      break;

    case 'meeting.ended':
      console.log(`Meeting ended: ${payload.object.topic}`);
      // Process analytics, download recordings, etc.
      break;

    case 'recording.completed':
      console.log(`Recording ready: ${payload.object.topic}`);
      // Download recording (see recording-pipeline.md)
      break;

    default:
      console.log(`Unhandled event: ${event}`);
  }

  // Respond with 200 to acknowledge receipt
  res.status(200).send();
});

Complete Lifecycle Workflow

Automated Meeting Management

class MeetingManager {
  constructor(accessToken) {
    this.accessToken = accessToken;
  }

  async createScheduledMeeting(topic, startTime, duration = 60) {
    const meetingData = {
      topic,
      type: 2,
      start_time: startTime,
      duration,
      settings: {
        join_before_host: false,
        waiting_room: true,
        auto_recording: 'cloud'
      }
    };

    const meeting = await this.createMeeting(meetingData);
    console.log(`Created meeting: ${meeting.id}`);
    
    return meeting;
  }

  async updateMeetingTime(meetingId, newStartTime) {
    await this.updateMeeting(meetingId, {
      start_time: newStartTime
    });
    console.log(`Updated meeting ${meetingId} start time`);
  }

  async cancelMeeting(meetingId) {
    await this.deleteMeeting(meetingId, {
      schedule_for_reminder: true  // Send cancellation email
    });
    console.log(`Cancelled meeting ${meetingId}`);
  }

  async getUpcomingMeetings() {
    const meetings = await this.listAllMeetings();
    const now = new Date();
    
    return meetings.filter(m => {
      const startTime = new Date(m.start_time);
      return startTime > now;
    });
  }

  // Helper methods (implementations from above examples)
  async createMeeting(data) { /* ... */ }
  async updateMeeting(id, updates) { /* ... */ }
  async deleteMeeting(id, options) { /* ... */ }
  async listAllMeetings() { /* ... */ }
}

// Usage
const manager = new MeetingManager(accessToken);

// Create meeting
const meeting = await manager.createScheduledMeeting(
  'Team Standup',
  '2025-03-15T10:00:00Z',
  30
);

// Update if needed
await manager.updateMeetingTime(meeting.id, '2025-03-15T14:00:00Z');

// Get all upcoming meetings
const upcoming = await manager.getUpcomingMeetings();
console.log(`${upcoming.length} upcoming meetings`);

// Cancel if needed
await manager.cancelMeeting(meeting.id);

Common Patterns

Recurring Meeting Series

const recurringMeeting = {
  topic: 'Weekly Team Sync',
  type: 8,  // Recurring with fixed time
  start_time: '2025-03-15T10:00:00Z',
  duration: 30,
  recurrence: {
    type: 2,  // Weekly
    repeat_interval: 1,
    weekly_days: '1,3,5',  // Monday, Wednesday, Friday
    end_times: 20  // 20 occurrences
  }
};

const meeting = await createMeeting(accessToken, recurringMeeting);

Meeting with Registration

const meetingWithRegistration = {
  topic: 'Product Demo',
  type: 2,
  start_time: '2025-03-15T14:00:00Z',
  duration: 60,
  settings: {
    approval_type: 0,  // Automatic approval
    registration_type: 1,  // Attendees register once
    meeting_authentication: false
  }
};

const meeting = await createMeeting(accessToken, meetingWithRegistration);
console.log('Registration URL:', meeting.registration_url);

PMI Meeting

const pmiMeeting = {
  topic: 'My Personal Room',
  type: 3,  // Recurring with no fixed time (PMI)
  settings: {
    use_pmi: true,
    join_before_host: true
  }
};

const meeting = await createMeeting(accessToken, pmiMeeting);

Error Handling

Per-User Daily Limit

Meeting/webinar create/update operations are limited to 100 per day per user (resets at 00:00 UTC).

async function createMeetingWithRetry(accessToken, meetingData, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await createMeeting(accessToken, meetingData);
    } catch (error) {
      if (error.message.includes('Too many requests')) {
        console.log(`Hit per-user limit. Attempt ${attempt}/${maxRetries}`);
        if (attempt < maxRetries) {
          await sleep(5000);  // Wait 5 seconds
          continue;
        }
      }
      throw error;
    }
  }
}

Validation Errors

try {
  await createMeeting(accessToken, meetingData);
} catch (error) {
  if (error.message.includes('Invalid field')) {
    console.error('Validation error:', error);
    // Check start_time format, type value, etc.
  } else if (error.message.includes('User does not exist')) {
    console.error('Invalid userId');
  } else {
    throw error;
  }
}

Resources