Zoom OBF Token Support

Configure OBF (On Behalf Of) tokens for Zoom bots joining external meetings after March 2, 2026

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 is affected, and how to implement them with Meeting BaaS.

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. It is:

  • 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
  • Required for external meetings: After March 2, 2026, bots cannot join meetings hosted by external accounts without an OBF token

OBF tokens require the authorized user to be present in the meeting. If they leave, the bot is disconnected. This is a Zoom requirement.

Who Needs OBF Tokens?

You need OBF tokens if:

  • Your bots join Zoom meetings created by people outside your Zoom organization
  • You are building a product where your customers request meeting recordings
  • You use Meeting BaaS as infrastructure for a service where end users have their own Zoom accounts

You do NOT need OBF tokens if:

  • Your bots only join meetings within your own Zoom account or organization
  • You use your own SDK credentials (which makes all meetings "internal")
  • You only use Google Meet or Microsoft Teams bots

Three Integration Options

Meeting BaaS supports three ways to provide OBF tokens, from most manual to fully automated:

Option 1: Direct Token (zoom_obf_token)

You fetch the OBF token yourself using Zoom's API and pass it directly when creating a bot.

How it works:

  1. Your backend calls Zoom's API to get an OBF token
  2. You pass the token in the bot creation request
  3. The bot uses this token when joining
direct_obf_token.sh
curl -X POST "https://api.meetingbaas.com/bots" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "meeting_url": "https://zoom.us/j/123456789",
           "bot_name": "Recording Bot",
           "zoom_obf_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
         }'
direct_obf_token.py
import requests

# First, fetch OBF token from Zoom using your stored access token
zoom_headers = {"Authorization": f"Bearer {user_access_token}"}
obf_response = requests.get(
    "https://api.zoom.us/v2/users/me/token?type=onbehalf",
    headers=zoom_headers
)
obf_token = obf_response.json()["token"]

# Then, create bot with the OBF token
url = "https://api.meetingbaas.com/bots"
headers = {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
}
config = {
    "meeting_url": "https://zoom.us/j/123456789",
    "bot_name": "Recording Bot",
    "zoom_obf_token": obf_token
}
response = requests.post(url, json=config, headers=headers)
print(response.json())
direct_obf_token.js
// First, fetch OBF token from Zoom using your stored access token
const obfResponse = await fetch(
  "https://api.zoom.us/v2/users/me/token?type=onbehalf",
  {
    headers: { Authorization: `Bearer ${userAccessToken}` },
  }
);
const { token: obfToken } = await obfResponse.json();

// Then, create bot with the OBF token
const response = await fetch("https://api.meetingbaas.com/bots", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
  },
  body: JSON.stringify({
    meeting_url: "https://zoom.us/j/123456789",
    bot_name: "Recording Bot",
    zoom_obf_token: obfToken,
  }),
});
console.log(await response.json());

Pros:

  • Full control over token lifecycle
  • No credentials stored in Meeting BaaS

Cons:

  • You must implement OAuth token storage and refresh
  • Token may expire if there is delay between fetching and bot joining
  • Must fetch a fresh token for each bot request

Best for: Testing, debugging, or customers who already have Zoom OAuth infrastructure.


Option 2: Token URL (zoom_obf_token_url)

You provide a URL that returns an OBF token. The bot calls this URL at join time to fetch a fresh token.

How it works:

  1. You host an endpoint that returns OBF tokens
  2. You pass the endpoint URL when creating a bot
  3. The bot calls your endpoint at join time and receives the token
token_url.sh
curl -X POST "https://api.meetingbaas.com/bots" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "meeting_url": "https://zoom.us/j/123456789",
           "bot_name": "Recording Bot",
           "zoom_obf_token_url": "https://your-api.com/zoom/obf-token?user_id=abc123"
         }'
token_url.py
import requests

url = "https://api.meetingbaas.com/bots"
headers = {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
}
config = {
    "meeting_url": "https://zoom.us/j/123456789",
    "bot_name": "Recording Bot",
    "zoom_obf_token_url": "https://your-api.com/zoom/obf-token?user_id=abc123"
}
response = requests.post(url, json=config, headers=headers)
print(response.json())
token_url.js
fetch("https://api.meetingbaas.com/bots", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
  },
  body: JSON.stringify({
    meeting_url: "https://zoom.us/j/123456789",
    bot_name: "Recording Bot",
    zoom_obf_token_url: "https://your-api.com/zoom/obf-token?user_id=abc123",
  }),
})
  .then((response) => response.json())
  .then((data) => console.log(data));

Your endpoint must:

  • Accept a GET request
  • Return the raw OBF token as the response body (plain text, not JSON)
  • Handle OAuth token refresh internally
  • Be accessible from Meeting BaaS infrastructure

Parameters passed to your endpoint:

When the bot calls your endpoint, it appends these query parameters:

ParameterDescription
bot_uuidThe Meeting BaaS bot UUID for this request
extraJSON string of any extra data you passed when creating the bot

For example, if you create a bot with:

{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_obf_token_url": "https://your-api.com/zoom/obf-token",
  "extra": {"user_id": "usr_456", "org_id": "org_789"}
}

Your endpoint will receive:

GET https://your-api.com/zoom/obf-token?bot_uuid=abc-123-def&extra={"user_id":"usr_456","org_id":"org_789"}

This lets you use either our bot_uuid or your own identifiers in extra to look up which token to return.

Example endpoint implementation:

your_endpoint.py
from flask import Flask, request
import requests
import json

app = Flask(__name__)

@app.route("/zoom/obf-token")
def get_obf_token():
    # Option 1: Use bot_uuid to look up the user
    bot_uuid = request.args.get("bot_uuid")

    # Option 2: Use your own identifiers from extra
    extra_str = request.args.get("extra")
    if extra_str:
        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 response.json()["token"]

Pros:

  • Token is always fresh (fetched at join time)
  • You maintain full control of OAuth credentials

Cons:

  • You must host and maintain an endpoint
  • You must implement OAuth token storage and refresh

Best for: Customers who want to keep Zoom credentials on their own infrastructure.


Option 3: Managed OAuth (zoom_obf_token_user_id)

Meeting BaaS stores the OAuth credentials and handles token refresh automatically. This is the recommended option for most customers.

How it works:

  1. Your Zoom user goes through OAuth consent flow
  2. You send the authorization code to Meeting BaaS
  3. Meeting BaaS stores the tokens and refreshes them automatically
  4. When creating a bot, you just specify the Zoom user ID
  5. Meeting BaaS fetches a fresh OBF token at join time

Direct your Zoom user to the OAuth authorization URL:

https://zoom.us/oauth/authorize?response_type=code&client_id={YOUR_CLIENT_ID}&redirect_uri={YOUR_REDIRECT_URI}

After the user authorizes, Zoom redirects to your redirect URI with an authorization code:

{YOUR_REDIRECT_URI}?code=AUTHORIZATION_CODE

Step 2: Create Zoom OAuth Connection

Send the authorization code to Meeting BaaS:

create_connection.sh
curl -X POST "https://api.meetingbaas.com/zoom_oauth_connections" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
           "redirect_uri": "https://your-app.com/oauth/callback",
           "zoom_client_id": "YOUR_ZOOM_CLIENT_ID",
           "zoom_client_secret": "YOUR_ZOOM_CLIENT_SECRET"
         }'
create_connection.py
import requests

url = "https://api.meetingbaas.com/zoom_oauth_connections"
headers = {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
}
data = {
    "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
    "redirect_uri": "https://your-app.com/oauth/callback",
    "zoom_client_id": "YOUR_ZOOM_CLIENT_ID",
    "zoom_client_secret": "YOUR_ZOOM_CLIENT_SECRET"
}
response = requests.post(url, json=data, headers=headers)
print(response.json())
create_connection.js
const response = await fetch(
  "https://api.meetingbaas.com/zoom_oauth_connections",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-meeting-baas-api-key": "YOUR-API-KEY",
    },
    body: JSON.stringify({
      authorization_code: "AUTHORIZATION_CODE_FROM_ZOOM",
      redirect_uri: "https://your-app.com/oauth/callback",
      zoom_client_id: "YOUR_ZOOM_CLIENT_ID",
      zoom_client_secret: "YOUR_ZOOM_CLIENT_SECRET",
    }),
  }
);
const connection = await response.json();
console.log(connection);
// { uuid: "...", zoom_user_id: "SeJwoMGwTCu52501SbDC0Q", state: "connected", ... }

Response:

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
  "zoom_account_id": "AplWZ5oMSouJOw9zu0cmKQ",
  "state": "connected",
  "scopes": "user:read:token user:read:user",
  "created_at": "2026-02-08T16:00:00",
  "updated_at": "2026-02-08T16:00:00"
}

Save the zoom_user_id from the response. You will use this when creating bots.

Step 3: Create Bots with Managed OBF

managed_obf.sh
curl -X POST "https://api.meetingbaas.com/bots" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "meeting_url": "https://zoom.us/j/123456789",
           "bot_name": "Recording Bot",
           "zoom_obf_token_user_id": "SeJwoMGwTCu52501SbDC0Q"
         }'
managed_obf.py
import requests

url = "https://api.meetingbaas.com/bots"
headers = {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
}
config = {
    "meeting_url": "https://zoom.us/j/123456789",
    "bot_name": "Recording Bot",
    "zoom_obf_token_user_id": "SeJwoMGwTCu52501SbDC0Q"
}
response = requests.post(url, json=config, headers=headers)
print(response.json())
managed_obf.js
fetch("https://api.meetingbaas.com/bots", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-meeting-baas-api-key": "YOUR-API-KEY",
  },
  body: JSON.stringify({
    meeting_url: "https://zoom.us/j/123456789",
    bot_name: "Recording Bot",
    zoom_obf_token_user_id: "SeJwoMGwTCu52501SbDC0Q",
  }),
})
  .then((response) => response.json())
  .then((data) => console.log(data));

Meeting BaaS will automatically:

  1. Look up the stored OAuth connection for this Zoom user
  2. Refresh the access token if expired
  3. Fetch a fresh OBF token from Zoom's API
  4. Pass it to the bot at join time

Pros:

  • Fully automated after initial setup
  • No need to manage tokens yourself
  • Token is always fresh
  • Handles refresh automatically

Cons:

  • Zoom OAuth credentials must be shared with Meeting BaaS
  • Requires building an OAuth consent UI for your users

Best for: Most customers, especially those building products for end users.

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 meeting participants identifying which app is accessing meeting content.

By default, if you only pass an OBF token without your own SDK credentials, the bot uses Meeting BaaS's default SDK credentials and the AAN will display "Meeting Baas" instead of your app name.

To show your app name in the AAN, pass your SDK credentials alongside your OBF token option:

{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_sdk_id": "YOUR_SDK_KEY",
  "zoom_sdk_pwd": "YOUR_SDK_SECRET",
  "zoom_obf_token_url": "https://your-api.com/zoom/obf-token"
}

This works with all three OBF options. The SDK credentials and OBF tokens serve different purposes — SDK credentials control the app identity (AAN), while OBF tokens authorize the bot to join on behalf of a user.

Zoom Marketplace Requirement: During Marketplace review, Zoom requires the AAN to display your app's name. If you're going through review, the reviewer may flag this if your SDK credentials are not included. See Zoom App Setup for details.

Migrating to v2? In the v2 API, you can store your SDK credentials once using the Credentials API instead of passing them with every request. This is more secure and simplifies your integration.

Bot Behavior with OBF Tokens

Authorized User Not in Meeting

When the bot joins with an OBF token and the authorized user is not yet in the meeting:

  1. Bot attempts to join
  2. Zoom returns "Authorized user not in meeting" error
  3. Bot retries every 30 seconds
  4. Once the authorized user joins, the bot successfully enters
  5. If the user does not join within the timeout period, the bot exits with error

The timeout is controlled by automatic_leave.waiting_room_timeout (default: 200 seconds).

{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_obf_token_user_id": "SeJwoMGwTCu52501SbDC0Q",
  "automatic_leave": {
    "waiting_room_timeout": 300
  }
}

Authorized User Leaves

If the authorized user leaves the meeting while the bot is active, Zoom ends the SDK session. The bot will:

  1. Stop recording
  2. Upload any recorded content
  3. Send completion webhook
  4. Exit the meeting

This is a Zoom requirement and cannot be changed.

Error Codes

Error CodeMeaning
WaitingForHostTimeoutThe authorized user did not join the meeting within the timeout period
CannotJoinMeetingGeneric join failure. With OBF, this could mean an invalid or expired token
ZOOM_SDK_AUTH_FAILEDSDK authentication failed. Check SDK credentials or OBF token validity

API Reference

Zoom OAuth Connections

The following endpoints manage Zoom OAuth connections for the managed OBF flow:

  • POST /zoom_oauth_connections — Create a new connection by exchanging an authorization code
  • GET /zoom_oauth_connections — List all connections for your account
  • GET /zoom_oauth_connections/:uuid — Get a specific connection
  • DELETE /zoom_oauth_connections/:uuid — Delete a connection and remove stored tokens

See the API Reference for full endpoint documentation.

Bot Parameters

ParameterTypeDescription
zoom_obf_tokenstringRaw OBF token (Option 1)
zoom_obf_token_urlstringURL that returns an OBF token (Option 2)
zoom_obf_token_user_idstringZoom user ID from a stored OAuth connection (Option 3)

Only provide one of these parameters. If multiple are provided, precedence is: zoom_obf_token > zoom_obf_token_url > zoom_obf_token_user_id.

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 your bots only join meetings within your own Zoom organization, consider using SDK credentials instead.

Create or Update Your Zoom App

Ensure your Zoom app has the user:read:token and user:read:user scopes. See Zoom App Setup.

Choose an Integration Option

  • Option 1 (Direct Token): For testing or if you already have OAuth infrastructure
  • Option 2 (Token URL): If you want to keep credentials on your infrastructure
  • Option 3 (Managed OAuth): Recommended for most customers

For Options 2 and 3, you need to implement a way for Zoom users to authorize your app (e.g. a "Connect Zoom" button). For Option 1 (Direct Token), you still need a way to obtain a Zoom access token—for example, a one-time OAuth flow or script—so you can call Zoom's token API to fetch the OBF token.

Update Bot Creation Requests

Add the appropriate OBF parameter (zoom_obf_token, zoom_obf_token_url, or zoom_obf_token_user_id) to your bot creation requests.

Test Before March 2, 2026

Test your integration with real Zoom meetings before the enforcement date.

FAQ

Q: What happens if I do not implement OBF tokens by March 2, 2026?

Your bots will fail to join external Zoom meetings. They will receive a join failure error.

Q: Can I use one OBF token for multiple meetings?

Yes, OBF tokens are not meeting-specific by default. When fetched via the API without specifying a meeting number, the token is valid for all meetings.

Q: Do I need OBF tokens for Google Meet or Microsoft Teams?

No, OBF tokens are a Zoom-specific requirement.

Q: What if the authorized user's Zoom account is deactivated?

The OAuth connection will become invalid. The user would need to re-authorize your app.

Q: Is there an alternative to OBF tokens for continuous recording?

Zoom is developing Real-Time Media Streams (RTMS) for continuous recording use cases. We are working on RTMS support, but it has different constraints (runs as an app inside the meeting, no bidirectional streaming support yet).

Resources

On this page