MeetStream — Zoom Authenticated Bots (ZAK and OBF)

View as Markdown

How to send a MeetStream Zoom bot as a signed-in user (ZAK) or on behalf of a user already in the meeting (OBF).

MeetStream does not store your end-users’ Zoom OAuth refresh tokens for join. You run Zoom OAuth, keep the refresh token, and expose an HTTPS URL. At join, the bot fetches that URL and uses the token you return.

Endpoint: POST /api/v1/bots/create_bot
Auth: Authorization: Token <your MeetStream user API key>


1) Pick a join mode

zoom on create_botWhat happens
omitted or {}Guest join — no ZAK, no OBF
{ "zak_url": "https://..." }Signed-in — bot joins as the Zoom user whose token you mint
{ "obf_url": "https://..." }On-behalf-of — bot is tied to a parent user already in that meeting
both URLs400 — send only one

Do not send use_zoom_obf or zoom_oauth_connection_user_id. Those fields are rejected.

You still save your Zoom Marketplace app client_id / client_secret on the MeetStream user (integrations auth / zoom). That is the Meeting SDK app the bot process runs as. It is not the end-user OAuth grant.

URLs must be https with a host (max 4096 characters). Bots run in AWS and cannot reach localhost.


2) ZAK vs OBF (Zoom behavior)

ZAK (zak_url)OBF (obf_url)
Zoom tokenGET /users/me/token?type=zakGET /users/me/token?type=onbehalf&meeting_id={id}
Typical OAuth scopeuser:read:zakuser:read:token (add user:read:user if you key by Zoom user id)
Who the bot isThat Zoom user (signed-in, profile photo)Assistant associated with a parent in the call
Parent must already be in the meetingNo — subject to Zoom host privileges and meeting admission settingsYes
If that Zoom user leavesBot can stayZoom removes the bot
Meeting id on your URLNot requiredDo not put meeting_number on the URL. MeetStream appends it at join

Enable Meeting SDK on the Zoom General App when using OBF.

OBF tokens are short-lived and single-use. Mint them when MeetStream calls you, not at create-bot time.


3) What you host

Two jobs, both on your HTTPS server:

  1. OAuth (once per Zoom user or service account)
    Redirect to Zoom authorize → callback with codePOST https://zoom.us/oauth/token (grant_type=authorization_code) → store refresh_token. Never send refresh tokens to MeetStream.

  2. Mint endpoint (every join)
    When the bot calls your URL, refresh the access token if needed (grant_type=refresh_token; persist a new refresh token if Zoom returns one), call Zoom’s user-token API, return the ZAK or OBF as plain text.

Encode which user in the URL (user_id, signed token, etc.). Treat the URL as a credential (shared auth query, HMAC, or short-lived signed URL).


4) create_bot examples

Guest (no authenticated Zoom join)

Omit zoom, or send "zoom": {}.

Signed-in (ZAK)

1{
2 "meeting_link": "https://zoom.us/j/123456789?pwd=...",
3 "bot_name": "MeetStream Bot",
4 "audio_required": true,
5 "zoom": {
6 "zak_url": "https://api.yourapp.com/zoom/zak?user_id=alice&auth=YOUR_SECRET"
7 }
8}

On-behalf-of (OBF)

Join the Zoom meeting yourself first, then create the bot. Do not add meeting_number to obf_url.

1{
2 "meeting_link": "https://zoom.us/j/123456789?pwd=...",
3 "bot_name": "MeetStream Bot",
4 "audio_required": true,
5 "automatic_leave": {
6 "waiting_room_timeout": 1200
7 },
8 "zoom": {
9 "obf_url": "https://api.yourapp.com/zoom/obf?user_id=alice&auth=YOUR_SECRET"
10 }
11}

The parent must already be in the meeting before the OBF join attempt. A longer waiting-room timeout does not replace that requirement.


5) What MeetStream sends to your URL

The Zoom bot tries GET first. If the server returns 405, or the body cannot be parsed as a token, it POSTs JSON. A 400 on GET is not retried as POST — fix the handler.

Common to both:

HeaderX-Bot-Id: MeetStream bot id
HeaderX-Webhook-Secret if the bot has a webhook secret
Success200, Content-Type: text/plain, raw token (length > 50), or JSON {"token": "TOKEN"} / {"zak": "TOKEN"} / {"obf": "TOKEN"}
FailureNon-2xx. If a URL is set, the bot does not guest-join

ZAK: POST body { "bot_id", "webhook_secret"? }. No meeting_number.

OBF: GET query meeting_number (MeetStream adds this). POST body { "bot_id", "meeting_number", "webhook_secret"? }. Mint with that meeting id.

If you already put meeting_number on obf_url, MeetStream appends it again. Many frameworks then see an array and your handler returns 400 (OBF token is required but could not be fetched). Leave meeting id off the create-bot URL.


6) Test without a bot

After OAuth, curl your own host (replace host, secret, meeting id):

$# ZAK
$curl -sS -D - -H "X-Bot-Id: test-bot" \
> "https://YOUR_HOST/zoom/zak?user_id=alice&auth=YOUR_SECRET"
$
$# OBF
$curl -sS -D - -H "X-Bot-Id: test-bot" \
> "https://YOUR_HOST/zoom/obf?user_id=alice&auth=YOUR_SECRET&meeting_number=123456789"

Expect 200 and a long token. 401 without auth is expected if you use a shared secret.

Then create_bot with zak_url or obf_url pointing at the same URL without duplicating meeting_number on OBF.


7) Troubleshooting

SymptomWhat to check
Pass only one of zoom.zak_url or zoom.obf_urlSend exactly one URL.
must use https / must include a hostPublic HTTPS URL.
use_zoom_obf is no longer supportedUse zak_url or obf_url and host OAuth yourself.
Custom zoom.zak_url/zoom.obf_url are not supportedAPI build is still on the old hosted-OBF contract.
Create succeeds, join: OBF token is required but could not be fetchedYour URL returned non-2xx. Check duplicate meeting_number, ngrok down, 401 auth, Zoom mint error.
OBF never admitted / waiting room foreverParent user must be in the meeting. Increase waiting_room_timeout.
Guest bot when you expected ZAK/OBFURL omitted or not forwarded; check create-bot body.