# 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](https://marketplace.zoom.us/) 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: ```plaintext 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](/docs/api/rest/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](https://marketplace.zoom.us/) 2. Click **Develop** → **Build App** → **Server-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** ```shell 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") ````
JavaScript (Node.js) ```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** ```json { "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** ```json { "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** ```json { "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** ```json { "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 | Feature | Single-Session | Multi-Session | Recurring | | ---------------------- | ----------------------------------- | ----------------------------------------------- | -------------------------- | | **Number of sessions** | 1 | Up to 500 | Up to 60 occurrences | | **Session type** | Continuous | Concurrent/sequential | Same session repeating | | **Setup time** | 15-30 minutes | Hours to days | 30-60 minutes | | **Lobby duration** | 15 min before or none | Up to 365 days | 15 min before each | | **Expo floor** | Not available | Up to 300 booths | Not available | | **Best for** | Webinars, training, one-time events | multi-session events, summits, multi-day events | Series, recurring meetings | | **Registration** | Once per event | Once for all sessions | Once 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'] ````
JavaScript (Node.js) ```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** ```json { "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" }' ````
Python ```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); } } ````
**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']}") ````
JavaScript (Node.js) ```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** ```json { "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** ```json { "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** ```json { "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']}") ````
JavaScript (Node.js) ```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** ```json { "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. ```shell # 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. ```python 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. ```python 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 ```plaintext 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 | Type | Use When | Code | | -------------- | --------------------------------------- | ------------------------------ | | Single-Session | One continuous session | `"event_type": "SIMPLE_EVENT"` | | Multi-Session | Multiple concurrent/sequential sessions | `"event_type": "CONFERENCE"` | | Recurring | Same session repeating on schedule | `"event_type": "RECURRING"` | ### Date time format Always use ISO 8601 UTC format: ```plaintext "start_time": "2025-12-15T18:00:00Z" ``` --- ## Additional resources **External documentation** - [Zoom Developer Portal](/) - API reference, OAuth setup - [OAuth Documentation](/docs/integrations/oauth/) - Authentication details - [Rate Limits](/docs/api/rest/rate-limits/) - Request limits and best practices - [Webhook Reference](/docs/api/events/events/#description/introduction/) - Real-time notifications --- ## 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: ```shell 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: ```python # 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](https://devforum.zoom.us/) or [API Documentation](/docs/api/). **Thank you for using the Webinars Plus & Events API**