Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions bot_zoom_obf_flow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This example demonstrates how to implement the Zoom OBF token flow to allow Reca

The OBF ties a bot's lifetime in a meeting directly to a specific user in that meeting. The bot can only be in the meeting as long as its "parent" user is. This enables:

- Appearing as your Zoom app via Zoom's native UI
- Appearing as your Zoom app via Zoom's native UI

> **📘 For complete documentation, see:** [Zoom Native Bots (OBF)](https://docs.recall.ai/docs/zoom-obf-tokens)

Expand Down Expand Up @@ -70,10 +70,10 @@ The OBF ties a bot's lifetime in a meeting directly to a specific user in that m

## Prerequisites

- [Zoom General App](https://developers.zoom.us/docs/integrations/create/) with scope: `user:read:token` and **Meeting SDK** enabled
- [ngrok](https://ngrok.com/) for exposing your local server
- [Node.js](https://nodejs.org/) 18+
- Custom SDK credentials enabled in your Recall workspace (contact Recall support to enable)
- [Zoom General App](https://developers.zoom.us/docs/integrations/create/) with scope: `user:read:token` and **Meeting SDK** enabled
- [ngrok](https://ngrok.com/) for exposing your local server
- [Node.js](https://nodejs.org/) 18+
- Custom SDK credentials enabled in your Recall workspace (contact Recall support to enable)

## Setup

Expand All @@ -89,7 +89,7 @@ Copy the domain (e.g. `abc123.ngrok-free.app`).

1. Go to the [Zoom App Marketplace](https://marketplace.zoom.us/develop/create)
2. Create a **General App** with OAuth
3. Set the OAuth Redirect URL to: `https://YOUR_NGROK_DOMAIN/zoom/oauth/callback`
3. Set the OAuth Redirect URL to: `https://YOUR_NGROK_DOMAIN/zoom/oauth/callback` (Note: this will also be used in your .env in step 4)
4. Add the scope: `user:read:token`
5. In the **Embed** section, enable **Meeting SDK**
6. Copy the **Client ID** and **Client Secret**
Expand All @@ -99,7 +99,7 @@ Copy the domain (e.g. `abc123.ngrok-free.app`).
1. Navigate to **Meeting Bot Setup** > **Zoom** in the Recall dashboard
2. Paste your Zoom app's Client ID and Client Secret

### 3. Set up env variables
### 4. Set up env variables

```bash
cp .env.sample .env
Expand Down Expand Up @@ -159,16 +159,16 @@ curl -X POST "https://RECALL_REGION.recall.ai/api/v1/bot/" \

**Note**:

- Replace `RECALL_REGION`, `RECALL_API_KEY`, and `YOUR_MEETING_URL` with your own
values.
- Replace `YOUR_NGROK_DOMAIN` with your ngrok domain (e.g. `somehash.ngrok-free.app`).
- The bot will join the meeting on behalf of the OAuth user.
- Replace `RECALL_REGION`, `RECALL_API_KEY`, and `YOUR_MEETING_URL` with your own
values.
- Replace `YOUR_NGROK_DOMAIN` with your ngrok domain (e.g. `somehash.ngrok-free.app`).
- The bot will join the meeting on behalf of the OAuth user.

## Important OBF Behavior

- **Short-lived & single-use**: OBF tokens should be minted just-in-time (in the `/zoom/obf` endpoint) when launching a bot
- **Parent user required**: The bot can't join until the parent user has already joined the meeting
- **Linked lifetime**: If the parent user leaves, the bot's will also be removed from the call by Zoom
- **Short-lived & single-use**: OBF tokens should be minted just-in-time (in the `/zoom/obf` endpoint) when launching a bot
- **Parent user required**: The bot can't join until the parent user has already joined the meeting
- **Linked lifetime**: If the parent user leaves, the bot's will also be removed from the call by Zoom

## API Endpoints

Expand Down
5 changes: 1 addition & 4 deletions bot_zoom_obf_flow/src/api/zoom_obf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import { z } from "zod";
import { env } from "../config/env";

/**
* Generate a Zoom OBF token.
* This is the token that is used to start a Zoom meeting and/or authenticate a participant in a Zoom meeting,
* allowing them to join meetings as authenticated participants (e.g. signed-in users).
* You can generate a new OBF token as long as you have a valid access token.
* Generate a Zoom OBF token given a meeting ID.
*/
export async function zoom_obf(args: { meeting_id: string }): Promise<{ obf_token: string }> {
const { meeting_id } = z.object({ meeting_id: z.string() }).parse(args);
Expand Down
4 changes: 1 addition & 3 deletions bot_zoom_obf_flow/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ Server is running on port ${env.PORT}

To get started, open the following URL in your browser: https://${process.env.NGROK_DOMAIN ?? "NGROK_DOMAIN"}/zoom/oauth

After you complete the OAuth flow, you can then create a bot.
- Ensure that \`zoom.obf_token_url="https://${process.env.NGROK_DOMAIN ?? "NGROK_DOMAIN"}/zoom/obf"\` is set in the bot's configuration.
- You can create a bot using the \`run.sh\` script. See the README for more details.
After you complete the OAuth flow, you can then create a bot using the \`run.sh\` script. See the README for more details.
`);
});
9 changes: 9 additions & 0 deletions bot_zoom_skip_waiting_room/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ZOOM_OAUTH_APP_CLIENT_ID=ZOOM_OAUTH_APP_CLIENT_ID
ZOOM_OAUTH_APP_CLIENT_SECRET=ZOOM_OAUTH_APP_CLIENT_SECRET
ZOOM_OAUTH_APP_REDIRECT_URI=ZOOM_OAUTH_APP_REDIRECT_URI # Includes the https:// protocol if using ngrok

# Optional if using the run.sh script to launch a bot
RECALL_API_KEY=RECALL_API_KEY
RECALL_REGION=RECALL_REGION # e.g. us-west-2, us-east-1, eu-central-1, ap-northeast-1
MEETING_URL=MEETING_URL # e.g. https://us06web.zoom.us/j/1234567890
NGROK_DOMAIN=NGROK_DOMAIN # e.g. if your ngrok URL is https://1a8d23b7ab2d.ngrok-free.app, drop the protocol and set to 1a8d23b7ab2d.ngrok-free.app
192 changes: 192 additions & 0 deletions bot_zoom_skip_waiting_room/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Zoom Join Token Flow for Skipping Waiting Rooms

This sample app shows how to skip the Zoom waiting room by generating a Zoom join token for local recording just before the bot joins the meeting.

> ⚠️ **Before you start**
>
> - This flow uses the Zoom SDK, so you also need an OBF token callback in addition to the join token callback.
> - This flow requires a feature flag on your Recall workspace; contact Recall support to enable it.

## Important Join Token Behavior

- **Short-lived & single-use**: Join tokens should be minted just-in-time (in the `/zoom/join-token` endpoint) when launching a bot
- **Meeting-scoped**: A join token is valid for a specific meeting ID, so pass the correct `meeting_id` every time

## Prerequisites

- [Zoom General App](https://developers.zoom.us/docs/integrations/create/)
- [ngrok](https://ngrok.com/) for exposing your local server
- [Node.js](https://nodejs.org/) 18+
- Custom SDK credentials enabled in your Recall workspace (contact Recall support to enable)

## Setup

### 1. Start ngrok

```bash
ngrok http 4000
```

Copy the domain (e.g. `abc123.ngrok-free.app`).

### 2. Create a Zoom General App

1. Go to the [Zoom App Marketplace](https://marketplace.zoom.us/develop/create)
2. Create a **General App** with OAuth
3. Set the OAuth Redirect URL to: `https://YOUR_NGROK_DOMAIN/zoom/oauth/callback` (Note: this will also be used in your .env in step 4)
4. Add the scope: `meeting:read:local_recording_token` and `user:read:token`
5. In the **Embed** section, enable **Meeting SDK**
6. Copy the **Client ID** and **Client Secret**

### 3. Add SDK credentials to Recall

1. Navigate to **Meeting Bot Setup** > **Zoom** in the Recall dashboard
2. Paste your Zoom app's Client ID and Client Secret

### 4. Set up env variables

```bash
cp .env.sample .env
```

Then fill out the variables in the `.env` file, including the ngrok domain from step 1.

### 5. Start the server

Open this directory in a new terminal and run:

```bash
npm install
npm run dev
```

This will start a server on port 4000.

### 6. Complete the OAuth flow

Open your browser and navigate to:

```
https://YOUR_NGROK_DOMAIN/zoom/oauth
```

Follow the prompts to authorize your Zoom app. After authorizing, the refresh token will be saved to `output/zoom_oauth_refresh_token.txt`.

### 7. Create a bot

Once you complete step 6, you can then create a bot using the `run.sh` script or manually with curl.

#### Option A: Using run.sh (recommended)

In a new terminal, run the script:

```bash
chmod +x run.sh
./run.sh
```

This will create a bot and paste the response in the terminal.

#### Option B: Using curl

```bash
curl -X POST "https://RECALL_REGION.recall.ai/api/v1/bot/" \
-H "Authorization: YOUR_RECALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"meeting_url": "YOUR_ZOOM_MEETING_URL",
"zoom": {
"join_token_url": "https://YOUR_NGROK_DOMAIN/zoom/join-token?meeting_id=ZOOM_MEETING_ID",
"obf_token_url": "https://YOUR_NGROK_DOMAIN/zoom/obf-token?meeting_id=ZOOM_MEETING_ID"
}
}'
```

**Note**:

- Replace `RECALL_REGION`, `RECALL_API_KEY`, `YOUR_MEETING_URL`, and `ZOOM_MEETING_ID` with your own
values.
- Replace `YOUR_NGROK_DOMAIN` with your ngrok domain (e.g. `somehash.ngrok-free.app`).
- The bot will join the meeting on behalf of the OAuth user.

## How It Works

```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Client │ │ Server │ │ Zoom │ │ Recall │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
│ 1. GET /zoom/oauth │ │
│───────────────▶│ │ │
│ │ │ │
│ Redirect to Zoom OAuth │ │
│◀───────────────│ │ │
│ │ │ │
│ Authorize app │ │
│────────────────────────────────▶│ │
│ │ │ │
│ Callback with code │ │
│◀────────────────────────────────│ │
│ │ │ │
│ 2. GET /zoom/oauth/callback │ │
│───────────────▶│ │ │
│ │ Exchange for tokens │
│ │───────────────▶│ │
│ │ { refresh_token } │
│ │◀───────────────│ │
│ │ (stored locally) │
│ │ │ │
│ ════════════════════════════════════════════════ │
│ Later, when creating a bot: │
│ ════════════════════════════════════════════════ │
│ │ │ │
│ │ 3. POST /api/v1/bot │
│ │ { zoom: { join_token_url, obf_token_url } } │
│ │────────────────────────────────▶│
│ │ │ │
│ ════════════════════════════════════════════════ │
│ When bot joins call: │
│ ════════════════════════════════════════════════ │
│ │ │ │
│ │ 4. GET /zoom/join-token │
│ │◀────────────────────────────────│
│ │ │ │
│ │ Refresh access token │
│ │───────────────▶│ │
│ │ │ │
│ │ Get join token │ │
│ │───────────────▶│ │
│ │ │ │
│ │ join_token │ │
│ │◀───────────────│ │
│ │ │ │
│ │ Return join token │
│ │────────────────────────────────▶│
│ │ │ │
│ │ 5. GET /zoom/obf-token │
│ │◀────────────────────────────────│
│ │ │ │
│ │ Refresh access token │
│ │───────────────▶│ │
│ │ │ │
│ │ Get OBF token │ │
│ │───────────────▶│ │
│ │ │ │
│ │ obf_token │ │
│ │◀───────────────│ │
│ │ │ │
│ │ Return OBF token │
│ │────────────────────────────────▶│
│ │ │ │
│ │ Bot joins meeting │
│ │ │ │
```

## API Endpoints

| Endpoint | Description |
| -------------------------- | ------------------------------------------------------ |
| `GET /zoom/oauth` | Initiates Zoom OAuth flow |
| `GET /zoom/oauth/callback` | Handles OAuth callback, stores refresh token |
| `GET /zoom/join-token` | Returns a join token (called by Recall when bot joins) |
| `GET /zoom/obf-token` | Returns an OBF token (required support callback) |
20 changes: 20 additions & 0 deletions bot_zoom_skip_waiting_room/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "bot_zoom_skip_waiting_room",
"version": "1.0.0",
"description": "Allow a bot to skip the waiting room of a Zoom meeting using a join token",
"main": "index.ts",
"scripts": {
"dev": "ts-node src/index.ts"
},
"author": "Gerry Saporito",
"license": "MIT",
"devDependencies": {
"@types/node": "^24.10.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
},
"dependencies": {
"dotenv": "^17.2.3",
"zod": "^4.1.13"
}
}
35 changes: 35 additions & 0 deletions bot_zoom_skip_waiting_room/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail

DOTENV_FILE="${DOTENV_FILE:-.env}"
if [ -f "$DOTENV_FILE" ]; then
# shellcheck source=/dev/null
source "$DOTENV_FILE"
fi

: "${RECALL_REGION:?RECALL_REGION is required (us-west-2, us-east-1, eu-central-1, ap-northeast-1)}"
: "${RECALL_API_KEY:?RECALL_API_KEY is required}"
: "${MEETING_URL:?MEETING_URL is required (Zoom/Meet URL)}"
: "${NGROK_DOMAIN:?NGROK_DOMAIN is required (ngrok.io host without scheme)}"

# Extract meeting ID from Zoom URL (matches zoom.us/j/123, zoom.com/s/123, etc.)
MEETING_ID=$(echo "${MEETING_URL}" | grep -oE 'zoom\.(us|com)/(j|s|wc/join)/([0-9]+)' | grep -oE '[0-9]+$')
if [ -z "${MEETING_ID}" ]; then
echo "Error: Could not extract meeting ID from URL: ${MEETING_URL}" >&2
exit 1
fi

curl --request POST \
--url https://${RECALL_REGION}.recall.ai/api/v1/bot/ \
--header "Authorization: ${RECALL_API_KEY}" \
--header "accept: application/json" \
--header "content-type: application/json" \
--data @- <<EOF
{
"meeting_url": "${MEETING_URL}",
"zoom": {
"join_token_url": "https://${NGROK_DOMAIN}/zoom/join-token?meeting_id=${MEETING_ID}",
"obf_token_url": "https://${NGROK_DOMAIN}/zoom/obf-token?meeting_id=${MEETING_ID}"
}
}
EOF
Loading