Skip to navigation

Zoom OAuth Meeting Bots

View as Markdown

MeetStream — Zoom OAuth meeting bots

How to connect one or more Zoom accounts and send a bot that records a mapped meeting without host prompts, or joins an unmapped meeting and asks the host to admit it and allow recording.

Create a bot: POST /api/v1/bots/create_bot
Auth: Authorization: Token <your MeetStream API key>

You do not choose a Zoom account on create_bot. MeetStream matches the meeting in the invite URL to a connected Zoom user. Pass the full Zoom link, including pwd= when Zoom includes it.

This is separate from signed-in joins (zoom.zak_url) and on-behalf-of joins (zoom.obf_url). Omit zoom for the flows below.


What you get

MeetingHow MeetStream treats itWhat the host sees
Personal meeting ID (PMI), or a scheduled meeting that was synced or reported by webhookMapped. The bot joins with that Zoom app and starts local recording.Usually no Admit step and no Allow recording step.
A new Instant meeting, or any meeting ID that is not mappedUnmapped. The bot joins as a guest with the Zoom app that owns that meeting. No recording join token is used.Allow recording. Admit only if that meeting’s waiting room is on.

You can connect several Zoom Marketplace apps (for example one app per Zoom account). Each connected Zoom user has its own meetings. A PMI on account A never uses account B’s app.


1. Register each Zoom app

Create a Zoom General app with Meeting SDK enabled, then register it with MeetStream.

POST /api/v1/zoom/oauth/apps
GET /api/v1/zoom/oauth/apps
GET /api/v1/zoom/oauth/apps/{app_id}
DELETE /api/v1/zoom/oauth/apps/{app_id}

Send the app’s client ID, client secret, and webhook secret. MeetStream returns an app ID (zoa_...) and keeps the secrets. Later responses do not include the client secret or webhook secret.

Suggested Zoom scopes:

  • meeting:read:local_recording_token
  • meeting:read:list_meetings
  • meeting:read:meeting
  • user:read:user

Subscribe the app’s webhook to meeting.created and meeting.started, and point it at the webhook URL MeetStream gives you for that app. MeetStream checks Zoom’s URL-validation challenge and the request signature before it stores anything.

Delete every connected Zoom user for an app before you delete the app.


2. Connect a Zoom user

Each connection is one Zoom user under one app. MeetStream returns a credential ID (zoc_...).

MeetStream-managed

MeetStream runs the OAuth refresh for you.

  1. GET /api/v1/zoom/oauth/authorize-url?zoom_oauth_app_id={app_id}&redirect_uri={https_url}
  2. Send the user to the Zoom URL in the response.
  3. Exchange the authorization code with POST /api/v1/zoom/oauth/credentials (or POST /api/v1/zoom/oauth/connections) and authorization_code.

MeetStream stores the Zoom tokens and returns credential_id. Reconnecting the same Zoom user on the same app updates that credential. It does not create a second one.

Customer-managed

You keep the Zoom tokens. MeetStream only stores your HTTPS callback.

POST /api/v1/zoom/oauth/credentials
{
"zoom_oauth_app_id": "zoa_...",
"access_token_callback_url": "https://api.yourapp.com/zoom/access-token?user=alice"
}

When a mapped bot is about to join, MeetStream calls that URL and uses the access token you return. The URL must be public https (not localhost or a private address). Return the token as plain text, or as JSON { "access_token": "..." }.

List and remove

GET /api/v1/zoom/oauth/credentials?zoom_oauth_app_id={app_id}
DELETE /api/v1/zoom/oauth/credentials/{credential_id}

Removing a MeetStream-managed connection revokes it at Zoom when the token is still valid, then deletes the connection and its meeting mappings. A disconnected Zoom user is not used for new bots.


3. Map meetings

POST /api/v1/zoom/oauth/credentials/{credential_id}/sync-meetings
GET /api/v1/zoom/meetings-to-credentials

Sync maps:

  • that user’s PMI
  • their upcoming scheduled meeting IDs

meeting.created and meeting.started webhooks map later meetings for that same Zoom user, including some Instant meetings if Zoom delivers the event before you create the bot.

{
"results": [{
"meeting_id": "9407356565",
"credential_id": "zoc_...",
"oauth_app_id": "zoa_...",
"is_pmi": true
}]
}

One meeting ID maps to one credential. Callers cannot override that on create_bot.


4. Create the bot

{
"meeting_link": "https://zoom.us/j/9407356565?pwd=...",
"bot_name": "MeetStream Bot",
"audio_required": true,
"video_required": false
}

Do not send a credential ID, a local-recording token, or a Zoom client secret on this request.

Mapped meeting

MeetStream uses the Zoom app that owns the mapping and attaches a short-lived local-recording join token when the bot enters the meeting (the token expires in about two minutes, so it is created at join time, not when you call create_bot).

Expected result: the bot joins and starts recording without an Admit dialog and without an Allow recording dialog.

bot.in_waiting_room can still fire for a moment. That event means the bot has clicked Join. It is not the same as Zoom showing an Admit button. If the host is not asked to admit anyone, the bot moves on to bot.inmeeting and bot.recording.

If MeetStream cannot create the join token, the bot still joins with that same Zoom app, but as a guest. The host may then see Admit and Allow recording.

Unmapped meeting

Typical case: an Instant meeting whose ID was never synced and whose meeting.started webhook has not been applied yet.

MeetStream checks which connected Zoom user can see that meeting and joins with that user’s Zoom app. It does not save the Instant ID as a mapped meeting, and it does not attach a local-recording join token.

Expected result:

  1. Allow recording — the host is asked to let the bot record.
  2. Admit — only if waiting room is enabled for that meeting.

Instant meetings usually start with waiting room off. Zoom does not let an outside service turn waiting room on after the Instant meeting has already started. To require Admit on an Instant meeting, turn on waiting room in that Zoom account’s meeting settings, or in the live meeting under Security → Enable Waiting Room, then send the bot.

If none of the connected Zoom users own the meeting, the bot joins as a guest with your default Zoom Meeting SDK app (the one saved under your MeetStream Zoom integration). The host still has to admit it when waiting room is on, and allow recording.

Use the Zoom app that belongs to the same Zoom account as the host. A bot signed in with a different account’s app can be rejected as an external meeting.


5. Waiting room by account

Two Zoom accounts can both be connected and still behave differently on Instant meetings:

  • Waiting room on, or “who goes to the waiting room” set to Everyone → the guest bot waits to be admitted, then asks to record.
  • Waiting room off, or only “users not in your account” → Zoom can let the bot in immediately, because it is using that account’s own app, and only ask to record.

PMI behavior does not follow this. A mapped PMI uses the recording join token and is meant to skip both prompts.


Checklist

  1. Register one Marketplace app per Zoom account you host from.
  2. Connect each Zoom user and keep the returned credential_id.
  3. Run sync and confirm each PMI in GET /api/v1/zoom/meetings-to-credentials.
  4. Mapped: start that PMI and call create_bot with the full invite URL. The bot should record without Admit or Allow recording.
  5. Unmapped: start a new Instant meeting and call create_bot with that new link. The host should see Allow recording. They see Admit only when waiting room is on for that meeting.