Your credentials

What you will learn

By the end of this tutorial, you will authenticate with the Webinars Plus & Events API, understand which event type suits your needs, and create your first published event ready for attendee registration—all in under 30 minutes.

Example scenario

You are a developer at TechCorp preparing to automate event creation for your company's monthly webinar series and upcoming annual conference. This guide will show you exactly how to get started.


What you will build

In this tutorial, you will:

  1. Set up OAuth authentication for the Webinars Plus & Events API.
  2. Choose the right event type for your use case single-session, multi-session, or recurring.
  3. Create and publish a live event that attendees can register for.
  4. Pre-register your first attendee programmatically.

Time required

20—30 minutes


Before you start

You will need:

  • A Zoom account Pro or higher
  • Zoom Webinars Plus or Events license
  • Access to Zoom Marketplace to create an OAuth app

Quick check

If you can access the Zoom web portal and see 'Webinars Plus & Events' in the navigation menu, you are ready to begin.


Understanding the Webinars Plus & Events platform

The building blocks

Every Zoom event follows this hierarchy:

Account
  └── Hub (your event portfolio - like "Marketing Events" or "Training Programs")
        └── Event (the gathering - like "Q4 Product Launch Webinar")
              └── Session (the actual live meeting - when content is delivered)
                    └── Tickets (individual attendee registrations)

Example: TechCorp has a hub called "Customer Education" that contains multiple events: "January Product Training," "February Feature Deep-Dive," etc. Each event has one or more sessions where content is delivered live.

API Basics

  • Base URL: https://api.zoom.us/v2/zoom_events/
  • Format: JSON requests and responses
  • Authentication: OAuth 2.0 using Server-to-Server
  • Rate limits

Important: After you create an event, its type cannot be changed. Choose carefully in Step 2.


Step 1: Authenticate with server-to-server OAuth

Benefits of server-to-server OAuth

This authentication method is perfect for:

  • Backend integrations, such as Node.js services or Python scripts.
  • Automated workflows including daily event creation, weekly reports.
  • Systems that run without user interaction such as cron jobs or CI/CD pipelines.

1.1 Create your OAuth app

  1. Go to Zoom Marketplace
  2. Click DevelopBuild AppServer-to-Server OAuth
  3. Fill in basic information (app name, company name, contact)
  4. Add these scopes:
    • zoom_events:read:list_hubs:admin
    • zoom_events:write:event:admin
    • zoom_events:read:event:admin
    • zoom_events:write:ticket:admin
    • zoom_events:read:list_registrants:admin
  5. Note your Account ID, Client ID, and Client Secret.

1.2 Get your access token

cURL ```shell curl --request POST \ --url 'https://zoom.us/oauth/token?grant_type=account_credentials&account_id=YOUR_ACCOUNT_ID' \ --header 'Authorization: Basic BASE64_ENCODED(CLIENT_ID:CLIENT_SECRET)' \ --header 'Content-Type: application/x-www-form-urlencoded' ```

How to encode credentials

echo -n "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" | base64
Python ```python import requests import base64

Your credentials

client_id = "YOUR_CLIENT_ID" client_secret = "YOUR_CLIENT_SECRET" account_id = "YOUR_ACCOUNT_ID"

Encode credentials

credentials = f"{client_id}:{client_secret}" encoded_credentials = base64.b64encode(credentials.encode()).decode()

Request token

response = requests.post( "https://zoom.us/oauth/token", headers={ "Authorization": f"Basic {encoded_credentials}", "Content-Type": "application/x-www-form-urlencoded" }, params={ "grant_type": "account_credentials", "account_id": account_id } )

token_data = response.json() access_token = token_data["access_token"] print(f"Access token: {access_token}") print(f"Expires in: {token_data['expires_in']} seconds")

</details>
<details>
<summary>
  <strong>JavaScript (Node.js)</strong>
</summary>
```javascript
const axios = require('axios');
async function getAccessToken() {
  const clientId = 'YOUR_CLIENT_ID';
  const clientSecret = 'YOUR_CLIENT_SECRET';
  const accountId = 'YOUR_ACCOUNT_ID';
  // Encode credentials
  const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
  try {
    const response = await axios.post(
      'https://zoom.us/oauth/token',
      null,
      {
        headers: {
          'Authorization': `Basic ${credentials}`,
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        params: {
          grant_type: 'account_credentials',
          account_id: accountId
        }
      }
    );
    console.log('Access token:', response.data.access_token);
    console.log('Expires in:', response.data.expires_in, 'seconds');
    return response.data.access_token;
  } catch (error) {
    console.error('Authentication failed:', error.response.data);
  }
}
getAccessToken();

Expected response

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "token_type": "bearer",
    "expires_in": 3599,
    "scope": "zoom_events:write:event:admin zoom_events:read:list_hubs:admin"
}

Pro tip: Tokens expire after 1 hour. Store the token securely and implement automatic refresh before expiration. Never commit tokens to version control.


Step 2: Choose your event type (critical decision)

This decision is permanent - you cannot change event type after creation. This section helps you identify the right fit for your scenario.

Quick decision guide

Questions to consider.

  1. Number of sessions
    • One continuous session → Single-Session (SIMPLE_EVENT)
    • Multiple sessions attendees pick which to join → Multi-Session (CONFERENCE)
    • Same session repeating on a schedule → Recurring (RECURRING)
  2. Expo floor requirements
    • Yes → Multi-Session (CONFERENCE) only
    • No → Any type works

Example scenarios

Scenario 1: TechCorp monthly webinar series

Situation: TechCorp runs "Product Tips Tuesday" - a 1-hour webinar on the first Tuesday of each month for 12 months. Same format, different topics each month. Attendees register once and can join any or all sessions.

Best choice recurring (RECURRING)

Why

  • Series format has 12 occurrences.
  • Register once, attend multiple.
  • Simple setup per occurrence.
  • Consistent branding and format.

Example

{
    "event_type": "RECURRING",
    "name": "Product Tips Tuesday",
    "recurrence": {
        "type": "monthly",
        "repeat_interval": 1,
        "monthly_day": 2,
        "end_times": 12
    }
}

Scenario 2 - TechCorp annual Multi-Session Event

Situation: TechCorp 3-day annual multi-session event with 120 sessions across 8 tracks, 200 exhibitor booths, networking lounge, and 5,000 expected attendees. Sessions run concurrently.

Best choice: multi-session (CONFERENCE)

Why

  • Multiple concurrent sessions up to 500 supported.
  • Needs expo floor up to 300 booths.
  • Extended lobby experience up to 365 days open.
  • Advanced networking features.

Example

{
    "event_type": "CONFERENCE",
    "name": "TechCorp Annual Summit 2025",
    "calendar": [
        {
            "start_time": "2025-06-10T09:00:00Z",
            "end_time": "2025-06-10T18:00:00Z"
        },
        {
            "start_time": "2025-06-11T09:00:00Z",
            "end_time": "2025-06-11T18:00:00Z"
        },
        {
            "start_time": "2025-06-12T09:00:00Z",
            "end_time": "2025-06-12T16:00:00Z"
        }
    ]
}

Scenario 3 - TechCorp product launch webinar

Situation: One-time 90-minute product launch webinar next month. Single presentation, Q&A at the end, 500 expected attendees.

Best choice single-session (SIMPLE_EVENT)

Why

  • One continuous session.
  • Fastest setup (15-30 minutes).
  • Simple flow: register → join → .
  • Perfect for webinars, training, launches.

Example

{
    "event_type": "SIMPLE_EVENT",
    "meeting_type": "WEBINAR",
    "name": "Introducing Project Phoenix",
    "calendar": [
        {
            "start_time": "2025-12-15T18:00:00Z",
            "end_time": "2025-12-15T19:30:00Z"
        }
    ]
}

Event type comparison table

FeatureSingle-SessionMulti-SessionRecurring
Number of sessions1Up to 500Up to 60 occurrences
Session typeContinuousConcurrent/sequentialSame session repeating
Setup time15-30 minutesHours to days30-60 minutes
Lobby duration15 min before or noneUp to 365 days15 min before each
Expo floorNot availableUp to 300 boothsNot available
Best forWebinars, training, one-time eventsmulti-session events, summits, multi-day eventsSeries, recurring meetings
RegistrationOnce per eventOnce for all sessionsOnce for all occurrences

Step 3: Create your first event

Create TechCorp's product launch webinar from scenario 3 above.

3.1 Get your Hub ID

Every event needs a hub (organizational container). Find yours:

cURL ```shell curl --request GET \ --url 'https://api.zoom.us/v2/zoom_events/hubs?role_type=host' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' ```
Python ```python import requests

access_token = "YOUR_ACCESS_TOKEN"

response = requests.get( "https://api.zoom.us/v2/zoom_events/hubs", headers={ "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" }, params={"role_type": "host"} )

hubs = response.json() print(f"Found {hubs['total_records']} hub(s):") for hub in hubs['hubs']: print(f" - {hub['name']} (ID: {hub['hub_id']})")

Store the first hub ID

hub_id = hubs['hubs'][0]['hub_id']

</details>
<details>
<summary>
  <strong>JavaScript (Node.js)</strong>
</summary>
```javascript
const axios = require('axios');
async function getHubId(accessToken) {
  try {
    const response = await axios.get(
      'https://api.zoom.us/v2/zoom_events/hubs',
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json'
        },
        params: {
          role_type: 'host'
        }
      }
    );
    const hubs = response.data;
    console.log(`Found ${hubs.total_records} hub(s):`);
    hubs.hubs.forEach(hub => {
      console.log(`  - ${hub.name} (ID: ${hub.hub_id})`);
    });
    return hubs.hubs[0].hub_id;
  } catch (error) {
    console.error('Failed to get hubs:', error.response.data);
  }
}

Expected response

{
    "total_records": 2,
    "hubs": [
        {
            "hub_id": "abc123xyz",
            "name": "Marketing Events Hub"
        },
        {
            "hub_id": "def456uvw",
            "name": "Product Training Hub"
        }
    ]
}

Pick the hub that matches your use case. For our product launch, we'll use the "Marketing Events Hub" with ID abc123xyz.

3.2 Create the event (draft status)

Create the event. It will start in draft status - links inactive, safe to configure.

cURL ```shell curl --request POST \ --url 'https://api.zoom.us/v2/zoom_events/events' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "hub_id": "abc123xyz", "name": "Introducing Project Phoenix",
"timezone": "America/Los_Angeles",
"event_type": "SIMPLE_EVENT",
"access_level": "PRIVATE_UNRESTRICTED",
"meeting_type": "WEBINAR",
"attendance_type": "VIRTUAL",
"calendar": [
  {
    "start_time": "2025-12-15T18:00:00Z",
    "end_time": "2025-12-15T19:30:00Z"
  }
],
"tagline": "The future of product analytics starts here"

}'

</details>
<details>
<summary>
  <strong>Python</strong>
</summary>
```python
import requests
from datetime import datetime, timedelta, timezone
access_token = "YOUR_ACCESS_TOKEN"
hub_id = "abc123xyz"
# Create event happening in 30 days
event_date = datetime.now(timezone.utc) + timedelta(days=30)
start_time = event_date.replace(hour=18, minute=0, second=0)
end_time = start_time + timedelta(hours=1, minutes=30)
event_data = {
    "hub_id": hub_id,
    "name": "Introducing Project Phoenix",
    "timezone": "America/Los_Angeles",
    "event_type": "SIMPLE_EVENT",
    "access_level": "PRIVATE_UNRESTRICTED",
    "meeting_type": "WEBINAR",
    "attendance_type": "VIRTUAL",
    "calendar": [
      {
        "start_time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
        "end_time": end_time.strftime("%Y-%m-%dT%H:%M:%SZ")
      }
    ],
    "tagline": "The future of product analytics starts here"
}
response = requests.post(
    "https://api.zoom.us/v2/zoom_events/events",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    },
    json=event_data
)
event = response.json()
print(f"Event created")
print(f"   Event ID: {event['event_id']}")
print(f"   Name: {event['name']}")
print(f"   Status: {event['status']}")
print(f"   URL: {event.get('event_url', 'Not yet active (draft mode)')}")
JavaScript (Node.js) ```javascript const axios = require('axios');

async function createEvent(accessToken, hubId) { // Create event happening in 30 days const eventDate = new Date(); eventDate.setDate(eventDate.getDate() + 30); eventDate.setUTCHours(18, 0, 0, 0);

const startTime = eventDate.toISOString(); eventDate.setUTCHours(19, 30, 0, 0); const endTime = eventDate.toISOString();

const eventData = { hub_id: hubId, name: "Introducing Project Phoenix",

timezone: "America/Los_Angeles",
event_type: "SIMPLE_EVENT",
access_level: "PRIVATE_UNRESTRICTED",
meeting_type: "WEBINAR",
attendance_type: "VIRTUAL",
calendar: [
  {
    start_time: startTime,
    end_time: endTime
  }
],
tagline: "The future of product analytics starts here"

};

try { const response = await axios.post( 'https://api.zoom.us/v2/zoom_events/events', eventData, { headers: { 'Authorization': Bearer ${accessToken}, 'Content-Type': 'application/json' } } );

const event = response.data;
console.log('Event created');
console.log(`   Event ID: ${event.event_id}`);
console.log(`   Name: ${event.name}`);
console.log(`   Status: ${event.status}`);
console.log(`   URL: ${event.event_url || 'Not yet active (draft mode)'}`);
return event.event_id;

} catch (error) { console.error('Failed to create event:', error.response?.data || error.message); } }

</details>
**Expected response**
```json
{
  "event_id": "kNqLPC6hSFiZ9NpgjA549w",
  "name": "Introducing Project Phoenix",
  "timezone": "America/Los_Angeles",
  "event_type": "SIMPLE_EVENT",
  "status": "draft",
  "meeting_type": "WEBINAR",
  "hub_id": "abc123xyz",
  "calendar": [
    {
      "start_time": "2025-12-15T18:00:00Z",
      "end_time": "2025-12-15T19:30:00Z",
    }
  ],
  "event_url": "https://events.zoom.us/e/abc123xyz",
  "attendance_type": "VIRTUAL"
}

Important: Save the event_id. You will need it for all subsequent operations.

3.3 Verify your event configuration

Before publishing, double-check everything (some settings lock after publishing).

cURL ```shell curl --request GET \ --url 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' ```

Review checklist

  • Ensure the event name if accurate.
  • Timezone correct for your audience.
  • Event type matches your needs Remember: cannot change this.
  • Start/end times correct.
  • Meeting type appropriate webinar supports up to 100,000 attendees.
  • Access level matches security requirements.

Pro tip: event_type and meeting_type (after registrations exist) are locked after publishing. Get them right now.


Step 4: Publish your event

Publishing activates all links and opens registration. After this, some settings lock.

cURL ```shell curl --request POST \ --url 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/event_actions' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{"operation": "publish"}' ```
Python ```python import requests

access_token = "YOUR_ACCESS_TOKEN" event_id = "kNqLPC6hSFiZ9NpgjA549w"

response = requests.post( f"https://api.zoom.us/v2/zoom_events/events/{event_id}/event_actions", headers={ "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" }, json={"operation": "publish"} )

result = response.json() print(f"Event published") print(f" Status: {result['status']}") print(f" Event ID: {result['event_id']}")

</details>
<details>
<summary>
  <strong>JavaScript (Node.js)</strong>
</summary>
```javascript
async function publishEvent(accessToken, eventId) {
  try {
    const response = await axios.post(
      `https://api.zoom.us/v2/zoom_events/events/${eventId}/event_actions`,
      { operation: "publish" },
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json'
        }
      }
    );
    console.log('Event published');
    console.log(`   Status: ${response.data.status}`);
    console.log(`   Event ID: ${response.data.event_id}`);
  } catch (error) {
    console.error('Failed to publish event:', error.response?.data || error.message);
  }
}

Expected response

{
    "status": "published",
    "event_id": "kNqLPC6hSFiZ9NpgjA549w"
}

What just happened

  1. All access links activated (registration page now live).
  2. Automated confirmation emails enabled.
  3. Registration opened for attendees.
  4. Some settings now locked (event type, meeting type).

Get your registration link

Fetch the event details to get the live registration URL.

cURL ```shell curl --request GET \ --url 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' ```

Response includes

{
    "event_id": "kNqLPC6hSFiZ9NpgjA549w",
    "name": "Introducing Project Phoenix",
    "status": "Published",
    "event_url": "https://events.zoom.us/e/abc123xyz"
}

Share this URL https://events.zoom.us/e/abc123xyz with your audience for public registration.


Step 5: Pre-register attendees (bonus)

Want to pre-register VIP attendees or import from your CRM? This section shows you how.

5.1 Get the default ticket type

Every published event has at least one ticket type. Get an ID:

cURL ```shell curl --request GET \ --url 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/ticket_types' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' ```

Expected response

{
    "total_records": 1,
    "ticket_types": [
        {
            "ticket_type_id": "tt_abc123",
            "name": "Free Admission",
            "cost": 0,
            "currency": "USD"
        }
    ]
}

Note: The ticket_type_id: tt_abc123.

5.2 Create tickets for VIP attendees

Pre-register up to 30 attendees per request:

cURL ```shell curl --request POST \ --url 'https://api.zoom.us/v2/zoom_events/events/kNqLPC6hSFiZ9NpgjA549w/tickets' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "tickets": [ { "email": "sarah.chen@techcorp.com", "first_name": "Sarah", "last_name": "Chen", "ticket_type_id": "tt_abc123" }, { "email": "james.wilson@partner.com", "first_name": "James", "last_name": "Wilson", "ticket_type_id": "tt_abc123" } ] }' ```
Python ```python import requests

access_token = "YOUR_ACCESS_TOKEN" event_id = "kNqLPC6hSFiZ9NpgjA549w" ticket_type_id = "tt_abc123"

VIP attendees from your CRM

vip_attendees = [ {"email": "sarah.chen@techcorp.com", "first_name": "Sarah", "last_name": "Chen"}, {"email": "james.wilson@partner.com", "first_name": "James", "last_name": "Wilson"}, {"email": "lisa.martinez@client.com", "first_name": "Lisa", "last_name": "Martinez"} ]

Format for API

tickets_data = { "tickets": [ {**attendee, "ticket_type_id": ticket_type_id} for attendee in vip_attendees ] }

response = requests.post( f"https://api.zoom.us/v2/zoom_events/events/{event_id}/tickets", headers={ "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" }, json=tickets_data )

result = response.json() print(f" Pre-registered {len(result['tickets'])} attendees") for ticket in result['tickets']: print(f" - {ticket['first_name']} {ticket['last_name']}: {ticket['join_url']}")

</details>
<details>
<summary>
  <strong>JavaScript (Node.js)</strong>
</summary>
```javascript
async function preRegisterAttendees(accessToken, eventId, ticketTypeId) {
  // VIP attendees from your CRM
  const vipAttendees = [
    { email: "sarah.chen@techcorp.com", first_name: "Sarah", last_name: "Chen" },
    { email: "james.wilson@partner.com", first_name: "James", last_name: "Wilson" },
    { email: "lisa.martinez@client.com", first_name: "Lisa", last_name: "Martinez" }
  ];
  const ticketsData = {
    tickets: vipAttendees.map(attendee => ({
      ...attendee,
      ticket_type_id: ticketTypeId
    }))
  };
  try {
    const response = await axios.post(
      `https://api.zoom.us/v2/zoom_events/events/${eventId}/tickets`,
      ticketsData,
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json'
        }
      }
    );
    console.log(`Pre-registered ${response.data.tickets.length} attendees`);
    response.data.tickets.forEach(ticket => {
      console.log(`   - ${ticket.first_name} ${ticket.last_name}: ${ticket.join_url}`);
    });
  } catch (error) {
    console.error('Failed to pre-register attendees:', error.response?.data || error.message);
  }
}

Expected response

{
    "tickets": [
        {
            "ticket_id": "tk_001",
            "email": "sarah.chen@techcorp.com",
            "event_join_link": "https://events.zoom.us/j/unique-join-link-1"
        },
        {
            "ticket_id": "tk_002",
            "email": "james.wilson@partner.com",
            "event_join_link": "https://events.zoom.us/j/unique-join-link-2"
        }
    ],
    "errors": []
}

What happened

Each attendee automatically received a confirmation email with their unique join link. They can click it to join the event when it starts.

Scaling tip: Batch pre-registrations in groups of 30 for optimal performance and rate limit compliance.


Common issues and solutions

Issue 1 - "Event is not published yet" (error 26501)

When it happens

Trying to create tickets before publishing the event.

Solution

Publish first, then create tickets.

# 1. Publish
POST /zoom_events/events/{eventId}/event_actions
{"operation": "publish"}
# 2. Then create tickets
POST /zoom_events/events/{eventId}/tickets

Issue 2 - "No permission to create event under hub" (error 26201)

When it happens

OAuth app missing required scopes or hub access denied.

Solution

  1. Verify you added zoom_events:write:event:admin scope in OAuth app.
  2. Confirm you have host/admin access to the hub.
  3. Re-authenticate to get new token with updated scopes.

Issue 3 - Access token expired

When it happens: Making API calls after 1 hour (token expiry)

Solution

Implement token refresh in your code.

import time
def get_fresh_token():
    # Your token request logic from Step 1
    token_data = authenticate()
    expires_at = time.time() + token_data['expires_in']
    return token_data['access_token'], expires_at
# Use in your app
access_token, expires_at = get_fresh_token()
# Before each API call
if time.time() >= expires_at - 60:  # Refresh 1 min before expiry
    access_token, expires_at = get_fresh_token()

Issue 4 - Rate limit exceeded (HTTP 429)

When it happens

Too many API calls in a short time.

Solution

Implement exponential backoff.

import time
def api_call_with_retry(func, max_retries=3):
    for attempt in range(max_retries):
        try:
            return func()
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 429:
                wait_time = 2 ** attempt  # 1s, 2s, 4s
                print(f"Rate limited. Waiting {wait_time}s...")
                time.sleep(wait_time)
            else:
                raise
    raise Exception("Max retries exceeded")

What you just accomplished

Congratulations! You have now:

  • Set up OAuth authentication with automatic token management.
  • Learned how to choose the right event type for your use case.
  • Created and published your first live Zoom Event.
  • Pre-registered attendees programmatically.
  • Got a working integration that can handle real events.

Your event is now live at the registration URL, ready for attendees to sign up.


Quick reference

Essential endpoints

Authentication:
POST https://zoom.us/oauth/token
Hubs:
GET /zoom_events/hubs
Events:
POST   /zoom_events/events              (create)
GET    /zoom_events/events/{eventId}    (read)
PATCH  /zoom_events/events/{eventId}    (update)
POST   /zoom_events/events/{eventId}/event_actions  (publish/archive)
Tickets:
GET  /zoom_events/events/{eventId}/ticket_types
POST /zoom_events/events/{eventId}/tickets

Event type quick reference

TypeUse WhenCode
Single-SessionOne continuous session"event_type": "SIMPLE_EVENT"
Multi-SessionMultiple concurrent/sequential sessions"event_type": "CONFERENCE"
RecurringSame session repeating on schedule"event_type": "RECURRING"

Date time format

Always use ISO 8601 UTC format:

"start_time": "2025-12-15T18:00:00Z"

Additional resources

External documentation


FAQ

Can I change the event type after creating an event?

No, event type is permanently locked at creation. If you need to change it, you must create a new event, migrate registrations, and delete the old event. Always choose carefully.


What is the difference between MEETING and WEBINAR for meeting_type?

This value is required only for single session Event.

  • MEETING: Interactive experience, max 1,000 attendees, everyone can see each other
  • WEBINAR: Broadcast-style, up to 100,000 attendees, only hosts or panelists visible

Cannot be changed after publishing if registrations exist.


Why isn't my registration link working after creating the event?

Events start in draft status with inactive links. You must publish the event first:

POST /zoom_events/events/{eventId}/event_actions
{"operation": "publish"}

How do I know which hub to use?

Use GET /zoom_events/hubs?role_type=host to list hubs. Choose based on:

  • Team ownership (Marketing, Training, etc.)
  • Content hub branding (if publishing recordings)
  • Organizational structure

You must have host or admin permissions on the hub to create events.


Can I pre-register attendees before publishing the event?

No. Pre-registration (ticket creation) requires the event to be in published status. Attempting to create tickets in draft mode returns error 26501.

Correct order

  1. Create event (draft).
  2. Publish event.
  3. Create tickets.

How many attendees can I add in one API call?

Up to 30 tickets per request for optimal performance. For larger lists:

# Batch in groups of 30
for i in range(0, len(attendees), 30):
    batch = attendees[i:i+30]
    create_tickets(batch)
    time.sleep(0.1)  # Rate limit compliance

Do attendees receive confirmation emails automatically?

Yes - When you create tickets via the API, Zoom automatically sends confirmation emails with unique join links to each attendee. You can also customize email templates in the event settings.


What timezone should I use for start_time and end_time?

Always use UTC (format: 2025-12-15T18:00:00Z). For SIMPLE_EVENT, include these in a calendar array. Set the event's local timezone separately using the timezone field. Zoom handles conversion for attendees.


How do I test my integration without creating real events?

Unfortunately, Webinars Plus & Events doesn't offer a sandbox environment. Best practices:

  1. Create a test hub specifically for development.
  2. Use obvious test names ("TEST - Do Not Register").
  3. Delete test events after validation.
  4. Start with draft events (links inactive) for safe testing.

Tutorial complete

You now have the foundation to build powerful Webinars Plus & Events integrations. Start with the examples in this guide, then explore the advanced topics in the next steps section as your needs grow.

Questions or feedback Check the Zoom Developer Forum or API Documentation.

Thank you for using the Webinars Plus & Events API