OBF Token Support
Configure OBF (On Behalf Of) tokens for Zoom bots joining external meetings in v2
Zoom OBF Token Support
Starting March 2, 2026, Zoom requires Meeting SDK applications to use On Behalf Of (OBF) tokens when joining meetings they did not create. This page explains what OBF tokens are, who needs them, and how to implement them with the v2 API.
Deadline: March 2, 2026. After this date, bots joining external Zoom meetings without OBF tokens will fail to join. See Zoom's official announcement.
What is an OBF Token?
An OBF (On Behalf Of) token is a Zoom authorization token that proves a specific Zoom user has authorized your bot to join meetings on their behalf.
Key characteristics:
- User-specific: Each token is tied to a particular Zoom user who authorized your app
- Short-lived: Tokens should be fetched close to when they are needed
- Meeting SDK only: Zoom web SDK, iOS SDK, Android SDK, Windows SDK, Linux SDK—all require OBF
- Authorized user presence: The user who authorized the token must be present in the meeting
Authorized User Presence: When using OBF tokens, the Zoom user who authorized your app must be in the meeting. If they leave, the bot is disconnected. This is a Zoom platform requirement.
Who Needs OBF Tokens?
You NEED OBF tokens if:
- Your bots join Zoom meetings created by people outside your Zoom organization
- You're building a product where customers request meeting recordings
- You use Meeting BaaS as infrastructure for a service where end users have their own Zoom accounts
- Any scenario where the meeting host is not part of your Zoom account
You do NOT need OBF tokens if:
- Your bots only join meetings within your own Zoom account/organization
- You use your own SDK credentials (makes all meetings "internal")
- You only use Google Meet or Microsoft Teams bots
Four Integration Options
The v2 API supports four ways to provide OBF tokens via the zoom_config object:
| Option | Parameter | Best For |
|---|---|---|
| Stored Credential | credential_id | Most users—set up once, fully managed |
| User ID Lookup | credential_user_id | When you store Zoom user IDs in your system |
| Direct Token | obf_token | Testing, or existing OAuth infrastructure |
| Token URL | obf_token_url | Keep credentials on your infrastructure |
Option 1: Stored Credential (Recommended)
Store the user's OAuth tokens via the Credentials API, then reference the credential when creating bots.
How it works:
- User completes OAuth consent flow → you receive authorization code
- Create a credential with the authorization code → Meeting BaaS stores encrypted tokens
- When creating bots, pass
credential_id→ we fetch fresh OBF token automatically
Step 1: Create OAuth Credential
After the user authorizes your app:
curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
-H "Content-Type: application/json" \
-H "x-meeting-baas-api-key: YOUR-API-KEY" \
-d '{
"name": "John Doe - Acme Corp",
"client_id": "YOUR_ZOOM_CLIENT_ID",
"client_secret": "YOUR_ZOOM_CLIENT_SECRET",
"authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
"redirect_uri": "https://your-app.com/oauth/callback"
}'Response:
{
"success": true,
"data": {
"credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
"credential_type": "user",
"state": "active",
...
}
}Step 2: Create Bots with Credential
curl -X POST "https://api.meetingbaas.com/v2/bots" \
-H "Content-Type: application/json" \
-H "x-meeting-baas-api-key: YOUR-API-KEY" \
-d '{
"bot_name": "Recording Bot",
"meeting_url": "https://zoom.us/j/123456789",
"zoom_config": {
"credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
}
}'import requests
response = requests.post(
"https://api.meetingbaas.com/v2/bots",
headers={
"Content-Type": "application/json",
"x-meeting-baas-api-key": "YOUR-API-KEY",
},
json={
"bot_name": "Recording Bot",
"meeting_url": "https://zoom.us/j/123456789",
"zoom_config": {
"credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
}
}
)
print(response.json())const response = await fetch("https://api.meetingbaas.com/v2/bots", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-meeting-baas-api-key": "YOUR-API-KEY",
},
body: JSON.stringify({
bot_name: "Recording Bot",
meeting_url: "https://zoom.us/j/123456789",
zoom_config: {
credential_id: "b2c3d4e5-f6a7-8901-bcde-f12345678901",
},
}),
});
console.log(await response.json());What happens:
- Bot is queued to join
- At join time, we fetch a fresh OBF token using the stored OAuth tokens
- Bot joins the meeting on behalf of the authorized user
Pros:
- Fully automated after initial setup
- Token always fresh (fetched at join time)
- Automatic token refresh handling
- Error tracking via credential state
Cons:
- Requires building OAuth consent flow for users
- OAuth credentials stored in Meeting BaaS
Option 2: User ID Lookup
If you store Zoom user IDs in your database, you can look up credentials by user ID instead of credential ID.
{
"bot_name": "Recording Bot",
"meeting_url": "https://zoom.us/j/123456789",
"zoom_config": {
"credential_user_id": "SeJwoMGwTCu52501SbDC0Q"
}
}Meeting BaaS finds the stored credential matching this Zoom user ID and uses it to fetch the OBF token.
Pros:
- Simpler integration if you already track Zoom user IDs
- No need to store credential UUIDs
Cons:
- Slightly slower (lookup required)
- Fails if multiple credentials exist for the same Zoom user ID
Option 3: Direct Token
Fetch the OBF token yourself and pass it directly.
# First, get the OBF token from Zoom
OBF_TOKEN=$(curl -s "https://api.zoom.us/v2/users/me/token?type=onbehalf" \
-H "Authorization: Bearer YOUR_USER_ACCESS_TOKEN" | jq -r .token)
# Then, create bot with the token
curl -X POST "https://api.meetingbaas.com/v2/bots" \
-H "Content-Type: application/json" \
-H "x-meeting-baas-api-key: YOUR-API-KEY" \
-d '{
"bot_name": "Recording Bot",
"meeting_url": "https://zoom.us/j/123456789",
"zoom_config": {
"obf_token": "'"$OBF_TOKEN"'"
}
}'import requests
# First, fetch OBF token from Zoom
zoom_response = requests.get(
"https://api.zoom.us/v2/users/me/token?type=onbehalf",
headers={"Authorization": f"Bearer {user_access_token}"}
)
obf_token = zoom_response.json()["token"]
# Then, create bot with the token
response = requests.post(
"https://api.meetingbaas.com/v2/bots",
headers={
"Content-Type": "application/json",
"x-meeting-baas-api-key": "YOUR-API-KEY",
},
json={
"bot_name": "Recording Bot",
"meeting_url": "https://zoom.us/j/123456789",
"zoom_config": {
"obf_token": obf_token
}
}
)
print(response.json())// First, fetch OBF token from Zoom
const zoomResponse = await fetch(
"https://api.zoom.us/v2/users/me/token?type=onbehalf",
{ headers: { Authorization: `Bearer ${userAccessToken}` } }
);
const { token: obfToken } = await zoomResponse.json();
// Then, create bot with the token
const response = await fetch("https://api.meetingbaas.com/v2/bots", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-meeting-baas-api-key": "YOUR-API-KEY",
},
body: JSON.stringify({
bot_name: "Recording Bot",
meeting_url: "https://zoom.us/j/123456789",
zoom_config: {
obf_token: obfToken,
},
}),
});
console.log(await response.json());Pros:
- Full control over token lifecycle
- No credentials stored in Meeting BaaS
Cons:
- You manage OAuth token storage and refresh
- Token may expire if there's delay before bot joins
- Must fetch fresh token for each request
Best for: Testing, debugging, or when you already have Zoom OAuth infrastructure.
Option 4: Token URL
Host an endpoint that returns OBF tokens. The bot calls your endpoint at join time.
{
"bot_name": "Recording Bot",
"meeting_url": "https://zoom.us/j/123456789",
"zoom_config": {
"obf_token_url": "https://your-api.com/zoom/obf-token"
}
}Parameters Passed to Your Endpoint
When the bot calls your endpoint, it appends query parameters:
| Parameter | Description |
|---|---|
bot_id | The Meeting BaaS bot ID (UUID) |
extra | URL-encoded JSON of the extra data from the bot request |
Example request to your endpoint:
GET https://your-api.com/zoom/obf-token?bot_id=abc-123-def&extra=%7B%22user_id%22%3A%22usr_456%22%7DYour Endpoint Requirements
- Accept GET requests
- Return the OBF token as plain text (ASCII, not JSON)
- Respond within 15 seconds (timeout)
- Handle OAuth token refresh internally
Example Endpoint Implementation
from flask import Flask, request
import requests
import json
app = Flask(__name__)
@app.route("/zoom/obf-token")
def get_obf_token():
# Parse identifiers from query params
bot_id = request.args.get("bot_id")
extra_str = request.args.get("extra", "{}")
extra = json.loads(extra_str)
user_id = extra.get("user_id")
# Look up stored OAuth credentials for this user
access_token = get_stored_access_token(user_id)
# Refresh if expired
if is_expired(access_token):
access_token = refresh_access_token(user_id)
# Fetch OBF token from Zoom
response = requests.get(
"https://api.zoom.us/v2/users/me/token?type=onbehalf",
headers={"Authorization": f"Bearer {access_token}"}
)
# Return raw token (not JSON)
return response.json()["token"]Pros:
- Token always fresh (fetched at join time)
- Credentials stay on your infrastructure
Cons:
- You must host and maintain an endpoint
- You must implement OAuth token storage and refresh
App Attribution (Active Apps Notifier)
When using OBF tokens, the bot's SDK credentials determine which app name appears in Zoom's Active Apps Notifier (AAN) — the notice shown to all participants identifying which app is accessing meeting content.
How Each Option Affects AAN
| Option | AAN Shows | Why |
|---|---|---|
Stored Credential (credential_id) | Your app name | Your SDK credentials are stored in the credential |
User ID Lookup (credential_user_id) | Your app name | Resolved credential includes your SDK credentials |
Direct Token (obf_token) only | "Meeting Baas" | No SDK credentials provided — falls back to defaults |
Token URL (obf_token_url) only | "Meeting Baas" | No SDK credentials provided — falls back to defaults |
Fixing AAN for Direct Token or Token URL
If you use obf_token or obf_token_url and need the AAN to display your app name, combine it with a stored app-only credential:
{
"bot_name": "Recording Bot",
"meeting_url": "https://zoom.us/j/123456789",
"zoom_config": {
"credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"obf_token_url": "https://your-api.com/zoom/obf-token"
}
}The stored credential provides your SDK keys for AAN attribution, while your endpoint continues handling OBF token generation. See Zoom Credentials for how to create an app-only credential.
Zoom Marketplace Requirement: During review, Zoom requires the AAN to show your app name. If you're going through Marketplace review, the reviewer may flag this if your bot requests don't include a stored credential. See Zoom's AAN documentation.
Bot Behavior with OBF Tokens
Authorized User Not Yet in Meeting
When the bot joins with an OBF token and the authorized user hasn't joined yet:
- Bot attempts to join
- Zoom returns "authorized user not in meeting"
- Bot retries every 30 seconds
- Once the user joins, bot enters successfully
- If user doesn't join within timeout, bot exits with error
Configure the timeout:
{
"bot_name": "Recording Bot",
"meeting_url": "https://zoom.us/j/123456789",
"zoom_config": {
"credential_id": "..."
},
"timeout_config": {
"waiting_room_timeout": 300
}
}Authorized User Leaves Meeting
If the authorized user leaves while the bot is active:
- Zoom terminates the SDK session
- Bot stops recording
- Bot uploads any recorded content
- Completion webhook is sent
- Bot exits
This is a Zoom requirement and cannot be changed.
Error Codes
| Error Code | Meaning | Resolution |
|---|---|---|
ZOOM_ACCESS_TOKEN_ERROR | Failed to fetch or use OBF token | Check credential state, verify OAuth tokens |
SDK_AUTH_FAILED | SDK authentication failed | Verify SDK credentials or OBF token validity |
WAITING_FOR_HOST_TIMEOUT | Authorized user didn't join in time | Increase timeout or ensure user joins promptly |
CANNOT_JOIN_MEETING | Generic join failure | Could be invalid token, meeting ended, or meeting doesn't exist |
Credential-Specific Errors
When using stored credentials, check the credential's error state:
curl "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
-H "x-meeting-baas-api-key: YOUR-API-KEY"If state is invalid, the last_error_message explains what went wrong.
Required Scopes
Your Zoom app needs these scopes for OBF tokens:
| Scope | Purpose | Required? |
|---|---|---|
user:read:zak | Auto-added when enabling Meeting SDK | Yes (auto) |
user:read:token | Fetch OBF tokens | Yes |
user:read:user | Get Zoom user profile | Yes (for stored credentials) |
See Zoom App Setup for adding scopes.
Migration Checklist
Determine if You Need OBF Tokens
Do your bots join meetings hosted by external Zoom accounts? If yes, you need OBF tokens. If only internal meetings, use app-only credentials.
Configure Your Zoom App
Ensure your Zoom app has user:read:token and user:read:user scopes. See Zoom App Setup.
Choose an Integration Option
- Option 1 (Stored Credential): Recommended for most users
- Option 2 (User ID Lookup): If you track Zoom user IDs
- Option 3 (Direct Token): For testing or existing OAuth
- Option 4 (Token URL): Keep credentials on your infrastructure
Implement OAuth Consent Flow
Build a "Connect Zoom" button for your users. See OAuth Consent Flow.
Update Bot Creation Requests
Add zoom_config with your chosen option to bot requests.
Test Before March 2, 2026
Test with real Zoom meetings before the enforcement date.
FAQ
Q: What happens if I don't implement OBF tokens by March 2, 2026?
Bots joining external Zoom meetings will fail. You'll receive a join failure error. Internal meetings with SDK credentials are not affected.
Q: Can one OBF token be used for multiple meetings?
Yes, OBF tokens are not meeting-specific when fetched without specifying a meeting number.
Q: Do Google Meet and Teams bots need OBF tokens?
No, OBF tokens are Zoom-specific.
Q: What if the authorized user's Zoom account is deactivated?
The stored credential becomes invalid. The user would need to re-authorize your app.
Q: Is there an alternative for continuous recording without user presence?
Zoom is developing Real-Time Media Streams (RTMS) for this use case. We're working on RTMS support, but it has different constraints and capabilities.
Q: How does v2 differ from v1 for OBF tokens?
v2 introduces the Credentials API for secure token storage, the zoom_config object for cleaner configuration, and better error tracking with credential states.
Resources
- Zoom Credentials API — Store and manage credentials
- OAuth Consent Flow — Build user authorization
- Zoom App Setup — Create your Zoom app
- Zoom's OBF Blog Post — Official announcement
- Zoom's OBF FAQ — Detailed Q&A from Zoom
- Zoom Token API — Zoom's token endpoint
