---
name: zoom-meeting-bot-app
description: Sets up and registers the Zoom app behind Unified.to's Zoom (meeting bot) integration (`zoommeetingbot`) end to end — a user-managed General app on the Zoom App Marketplace with Realtime Media Streams (RTMS) scopes, the bot and recording scopes, the Event Subscription (RTMS started, `recording.completed`, `recording.transcript_completed`) pointed at Unified's `zoommeetingbot` workspace webhook URL, the app's webhook Secret Token entered in the integration's Secret Token field in Unified, and an end-to-end test from `READY` through `IN_CALL_RECORDING`, `CALL_ENDED` and `DONE`. Use when asked to set up Zoom meeting bots or an AI notetaker on Zoom, register a Zoom app with RTMS for Unified, find or rotate the Zoom Secret Token, or fix a Zoom bot stuck in `READY`/`CALL_ENDED` or a `400` about the Secret Token. Read `zoom-oauth-app` for the general Marketplace mechanics and `unified-meeting-bots` for creating bots and handling events.
---

# Zoom (meeting bot) App Setup and Registration

Get a working **Zoom (meeting bot)** integration (`zoommeetingbot`) in your Unified.to workspace: your own Zoom app
with RTMS, the right scopes, an Event Subscription pointed at Unified, and the app's **Secret Token** saved on the
integration, so that `calendar_bot`s follow Zoom meetings live.

Three things cost more than they look:

- **It is a separate integration with its own app.** Zoom (meeting bot) is not the `zoom` integration; existing Zoom
  connections don't change, and a Zoom connection can't run bots. Unified has **no shared Zoom credentials** for
  meeting bots.
- **The Secret Token is not the Client Secret.** Zoom gives the app a webhook *Secret Token* on its Features → Access
  page. Unified needs it in the integration's **Secret Token** field; without it, creating a bot returns
  `400`, Zoom's endpoint validation fails, and every RTMS start is refused (a wrong Secret Token makes the
  RTMS-started webhook fail with `401`, so the bot stays `READY`). The RTMS handshake itself is signed with the
  app's Client ID and Client Secret, not the Secret Token.
- **Order matters.** Zoom validates the event endpoint the moment you save it, and Unified answers that challenge with
  your Secret Token — so enter the Secret Token in Unified (§5) **before** validating the endpoint (§4).

## Built on

- **`zoom-oauth-app`** — Marketplace account and developer role, General app creation, the OAuth allow list,
  granular scopes, the two credential pairs, deauthorization endpoint, private/beta install caps and Marketplace
  review. Read it first; this file doesn't repeat it.
- **`unified-meeting-bots`** — creating bots, the `calendar_botevent` webhook, statuses, the `501` matrix and the
  recurring-meeting rule.
- **`unified-quickstart`** — workspace basics and the Authorization component.

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **Zoom account with Marketplace developer access** | Owner, admin, or the developer role (`zoom-oauth-app` §2) |
| **RTMS for the app** | RTMS scopes, and a Zoom Developer Pack that covers RTMS usage (§1) |
| **Unified workspace ID** | In `app.unified.to` → **Settings**; goes in the event endpoint URL (§4) |
| **Unified region** | US `api.unified.to`, EU `api-eu.unified.to`, AU `api-au.unified.to` |
| **Which Unified permissions** | Bot permissions always; `calendar_event_read`, `calendar_recording_read` if needed (§3) |
| **Cloud recording on hosts' accounts?** | Needed for `RECORDING_READY` / `DONE` (Pro or higher) |
| **Zoom plan for transcript-completed events** | `recording.transcript_completed` is offered on Business, Education, Enterprise |
| **Distribution intent** | Private/beta caps vs published app (`zoom-oauth-app` §7) |

## Quick Start

1. Confirm your Zoom Developer Pack covers RTMS usage (§1).
2. Create a **user-managed** General app with Unified's redirect URL; copy Client ID and Client Secret (§2).
3. Add the scopes (§3).
4. Turn on **Event Subscription** with RTMS started in Meeting, All Recordings have completed, Recording transcript
   files have completed — but don't validate yet (§4).
5. In Unified, activate **Zoom (meeting bot)**, enter Client ID, Client Secret and the **Secret Token** (§5).
6. Set the endpoint URL to `https://api.unified.to/webhook/workspace/zoommeetingbot?workspace_id=<WORKSPACE_ID>` and
   validate (§4).
7. Make sure RTMS starts in meetings (§6).
8. Connect a test user and run the end-to-end test (§7, §8).

## Product state (from the TICKET-3338 Zoom meeting-bot PR — re-verify before trusting)

- Integration type `zoommeetingbot`, a Zoom RTMS session runner; not launched yet.
- Besides the Client ID and Client Secret, the integration asks for the app's Secret Token in its own **Secret
  Token** field, with instructions under the field. It's entered once per workspace on the integration, not on each
  connection.
- Inherits Zoom's `calendar_calendar`, `calendar_event`, `calendar_recording`, `calendar_webinar`, `calendar_link`,
  `calendar_location`, `calendar_room` and `uc_contact`. `calendar_recording` get-by-id uses
  `cloud_recording:read:list_recording_files` and ships in unified-api #10639; until that's merged, list recordings
  by date and match the bot's `recording_id`.
- Supports `recording_config.record_transcript`, `record_chat`, and `transcription.provider: MEETING_CAPTIONS` only;
  `join_at`, `record_audio`, `record_video`, `record_screenshare` return `501`; any other provider fails validation
  with `400`.

If the integration or Marketplace doesn't look like this, stop and report what you see.

## 1. RTMS scopes and the Zoom Developer Pack

The bot follows the meeting through Zoom **Realtime Media Streams (RTMS)** — it never joins as a participant.
Your app must be able to use RTMS before anything else works. Zoom's RTMS docs (checked 2026-11-07): "RTMS is
available to all developers", and it needs an active **Zoom Developer Pack** subscription with enough credits. The
limit is **2,000 concurrent streams** on a Developer Pack (higher on request) and **5** on the free trial. Zoom counts
each unique RTMS session ID as one concurrent stream. **Confirm the current plan and usage
terms on Zoom's RTMS pages before promising anything** — this is Zoom's gate, not a Unified setting.

## 2. Create the Zoom app

1. Marketplace → **Develop → Build an app → General app**, management type **User-managed**.
2. Redirect URL and OAuth allow list for your Unified region:
   - US `https://api.unified.to/oauth/code`
   - EU `https://api-eu.unified.to/oauth/code`
   - AU `https://api-au.unified.to/oauth/code`
3. Copy the **Client ID** and **Client Secret** (label development vs production — `zoom-oauth-app` §6). Unified
   uses them for OAuth and to sign the RTMS connection.

## 3. Scopes

| Scope | Unified permission | Why |
| --- | --- | --- |
| `user:read:user` | `calendar_bot_write` | Find the connected user's Zoom account when a bot is created |
| `meeting:read:meeting` | `calendar_bot_write` | Read the meeting when a bot is created |
| `meeting:read:meeting_transcript` | `calendar_botevent_read` | Receive Zoom's meeting captions over RTMS |
| `cloud_recording:read:list_user_recordings` | `calendar_recording_read` | List recordings |
| `cloud_recording:read:list_recording_files` | `calendar_recording_read` | Read the recording a bot points to, by id |
| `meeting:read:list_meetings` | `calendar_event_read` | List meetings, to create bots from `event_id` |

For other inherited permissions (calendars, webinars, rooms, contacts), add the scopes the Zoom integration uses for
them. The sign-in scopes the Zoom integration requests (`user_profile`, `user_info:read`, `user:read`) also apply.
The only RTMS scope the bot requests is `meeting:read:meeting_transcript` (above); it doesn't need RTMS audio, video or
screen-share scopes. Zoom's RTMS docs say the same: "if your app only requires meeting transcripts, only select the
`meeting:read:meeting_transcript` scope". RTMS scopes can't be marked optional.

**Chat:** Zoom documents no separate RTMS chat scope name, so Unified requests none. `CHAT_MESSAGE_RECEIVED` arrives
only if your app has chat access in RTMS — Zoom sends only the media and session events your app's scopes allow.

## 4. Event Subscription

Zoom has no API to register this subscription; set it up in the app.

1. App → **Features** → turn on **Event Subscription**.
2. Add these events:
   - **RTMS → RTMS started in Meeting** (`meeting.rtms_started`) — starts the live stream. (Not the Video SDK's
     "RTMS Started". There's no need to subscribe to the stopped event: Unified detects the end over the stream.)
   - **Recording → All Recordings have completed** (`recording.completed`) — sets `recording_id`, sends
     `RECORDING_READY`, moves the bot to `DONE` (Pro or higher)
   - **Recording → Recording transcript files have completed** (`recording.transcript_completed`) — sends
     `RECORDING_READY` again once the transcript is ready (Business, Education, Enterprise plans)
3. Endpoint URL — the **Zoom (meeting bot)** URL, not the Zoom integration's:

```txt
https://api.unified.to/webhook/workspace/zoommeetingbot?workspace_id=<YOUR_WORKSPACE_ID>
```

Use `api-eu.unified.to` / `api-au.unified.to` for EU/AU workspaces. Zoom sends `endpoint.url_validation` when you
save, and again every 72 hours; Unified answers it with your Secret Token, so **do §5 first**.

## 5. Enter the credentials in Unified

1. `app.unified.to` → **Integrations** → activate **Zoom (meeting bot)**.
2. Enter the app's **Client ID** and **Client Secret**.
3. Enter the app's **Secret Token** in the **Secret Token** field (Unified asks for it on activation, with
   instructions under the field). Find it in the Marketplace: **Manage** → your app → **Features**,
   Access section. It is **not** the OAuth Client Secret.
4. Select the permissions: `calendar_bot_read`, `calendar_bot_write`, `calendar_botevent_read`, plus
   `calendar_event_read` (to list meetings) and `calendar_recording_read` if you need them. Creating a bot from an
   `event_id` needs only `calendar_bot_write`, which already requests `meeting:read:meeting`.

The Secret Token verifies every Zoom delivery; Unified never sends it to Zoom. Zoom signs each delivery with
`x-zm-signature` = `v0=` + hex HMAC-SHA256 of `v0:{x-zm-request-timestamp}:{body}`. Zoom documents no replay window;
Unified applies its own and rejects deliveries more than 5 minutes old. If you regenerate the Secret Token in Zoom,
update it in Unified — the change applies within about a minute, including to existing connections.

## 6. Make sure RTMS starts in meetings

The bot waits in `READY` until Zoom starts the meeting's realtime stream, which happens when the host has chosen your
app to auto-start, or your app is started in the meeting. If neither happens, the bot stays `READY` and no live events
are sent.

- **Host (user level):** in the Zoom web portal, **Settings → Zoom Apps** tab → **Auto-start apps that access shared
  realtime meeting content** → **+ Choose an app to auto-start** → select your app → **Save**. It's a per-app choice,
  not a toggle.
- **Admin (account level):** **Account Management → Account Settings → Zoom Apps** → turn on **Share realtime meeting
  content with apps** (it can also be set per group or user). Admins can also add your app under **Apps that access
  content in meetings → + Add app**.

## 7. How end users connect

End users authorize **Zoom (meeting bot)** through the Authorization component or authorization link, granting the
§3 scopes. A bot can only follow a meeting the connected user's Zoom account can read. Unreviewed apps are capped
(private user-level apps at 100 installs; `zoom-oauth-app` §7) — plan Marketplace review before external customers.

## 8. Test end to end

1. On the connection, create the native `calendar_botevent` / `created` webhook.
2. Create a `calendar_bot` with the Zoom join link (`meeting_url`) or a `calendar_event` id (`event_id` = Zoom
   meeting ID) → `READY`.
3. Start the meeting and RTMS → `BOT_STATUS_CHANGE` to `IN_CALL_RECORDING`, then `TRANSCRIPT_RECEIVED` and
   participant events.
4. End the meeting → `CALL_ENDED`.
5. With cloud recording on → `RECORDING_READY` and `DONE` (`sub_code` `recording_ready`); then
   `GET /calendar/{connection_id}/recording/{recording_id}` (needs #10639; otherwise list by date). A second `RECORDING_READY` follows when the transcript
   file completes.

## Limits

- `501`: `join_at`, `record_audio`, `record_video`, `record_screenshare`. `400`: a link that isn't a Zoom meeting
  link, or a provider other than `MEETING_CAPTIONS`. `404`: unknown meeting or event. `422`: Unified can't resolve
  the connected user's Zoom account.
- Captions only (`MEETING_CAPTIONS`); recordings need cloud recording, else the bot stays `CALL_ENDED`.
- **Recurring meetings** share one meeting ID; a recording goes to the oldest unfinished bot. Create the next
  occurrence's bot only after the previous one is `DONE`, or delete it if there was no cloud recording.
- One unfinished bot per meeting link per connection: `409` while an earlier bot for the exact same link isn't
  `CALL_ENDED`, `DONE` or `FATAL`. Updating `meeting_url` to a different value returns `400`; `name`, `event_id`
  and `recording_config` can change, but a running stream keeps the media it opened with.
- If the stream drops, Unified retries within Zoom's reconnection window (about 60 seconds for the signaling
  connection, about 30 for media); if that fails, `FATAL` `rtms_connection_lost`.
- Chat: new messages to everyone only. Speaking: `PARTICIPANT_SPEECH_ON` only.
- Deleting a bot stops the stream and sends `FATAL` (`user_cancelled`) before deletion.

## Troubleshooting by error message

| Symptom | Cause and fix |
| --- | --- |
| Creating a bot returns `400` about the Secret Token | Secret Token missing on Zoom (meeting bot) in Unified (§5) |
| Zoom can't validate the endpoint URL | Secret Token missing or wrong (it isn't the Client Secret); URL uses the Zoom path instead of `zoommeetingbot`; `workspace_id` missing/wrong; wrong region host |
| Bot stays `READY` | RTMS didn't start (§6); the app has no RTMS scopes or the Developer Pack doesn't cover RTMS (§1); **RTMS started in Meeting** not subscribed or pointed elsewhere; host hasn't chosen your app to auto-start, or the admin hasn't allowed sharing realtime meeting content (§6) |
| Bot stays `CALL_ENDED` | No cloud recording on the host's account; **All Recordings have completed** not subscribed |
| No chat events | `record_chat` not set in `recording_config`; app has no RTMS chat access |
| `FATAL` `rtms_connection_lost` | Stream dropped and reconnect failed within Zoom's window — create a new bot for the meeting if it's still running |
| Bot stays `READY` and Unified logs a `401` on the RTMS-started webhook | Secret Token in Unified doesn't match the app's (§5) |
| `FATAL` `session_failed` | The RTMS session failed to start or stalled (reported by the dispatch worker) — check RTMS scopes, Developer Pack coverage and the Client ID / Client Secret; it isn't caused by the Secret Token |
| `422` on create | Unified couldn't resolve the connected user's Zoom account |
| `501 Not supported by this integration: …` | Remove `join_at` or the capture flag |
| `400` with a `transcription.provider` | Only `MEETING_CAPTIONS` is valid |
| `invalid_client`, `Invalid redirect` at sign-in | `zoom-oauth-app` troubleshooting (credential pair, allow list) |

## Stop and ask

- Nobody can confirm the Zoom Developer Pack covers the expected RTMS usage (§1).
- The product needs audio/video capture, third-party transcription or scheduled joins — not supported (`501`).
- Hosts' accounts have no cloud recording but the product depends on recordings.
- Customers are external and the app is still private/beta — review is a Zoom process with its own timeline.

## References

- Zoom RTMS overview: https://developers.zoom.us/docs/rtms/
- RTMS getting started (availability, Developer Pack): https://developers.zoom.us/docs/rtms/meetings/getting-started/
- RTMS scopes (transcript-only apps): https://developers.zoom.us/docs/rtms/meetings/add-features/
- RTMS troubleshooting (concurrent stream limits): https://developers.zoom.us/docs/rtms/troubleshooting/
- RTMS webhook events: https://developers.zoom.us/docs/api/rtms/events/
- RTMS host and admin controls (auto-start, sharing realtime content): https://developers.zoom.us/docs/rtms/meetings/ux-host-admin-tools-ctrls/
- RTMS failover and reconnection: https://developers.zoom.us/docs/rtms/meetings/failover-reconnection/
- Recording events: https://developers.zoom.us/docs/api/meetings/events/
- Zoom webhooks and endpoint validation: https://developers.zoom.us/docs/api/webhooks/
- Zoom build flow (Features → Access, Secret Token): https://developers.zoom.us/docs/build-flow/create-oauth-apps/
- `calendar_bot` model: https://docs.unified.to/calendar/bot/model
- Guide — How to Set Up a Zoom Meeting Bot with Unified (published at launch):
  https://docs.unified.to/guides/how_to_set_up_a_zoom_meeting_bot_with_unified
