Skill 121 · Build Zoom Meeting SDK App
Subchapter 121.4
references/bot-authentication.mdMarkdown10 KBView on GitHub
Understanding the different token types for Meeting SDK authentication, especially for bots joining meetings.
Meeting SDK authentication involves multiple token types that serve different purposes. This guide clarifies the confusion between JWT signatures, ZAK tokens, and OBF tokens.
| Token | Purpose | Always Required? | Deprecated? |
|---|---|---|---|
| JWT Signature | Initialize/authenticate Meeting SDK | Yes | No |
| ZAK Token | Authenticate as a Zoom user | No (situational) | No |
| OBF Token | Join external meetings with user attribution | No (required Feb 2026) | No |
This is the #1 source of confusion.
| Term | What It Is | Status |
|---|---|---|
| JWT App Type | A Zoom app type for REST API authentication | Deprecated (migrated to Server-to-Server OAuth) |
| JWT Signature | A token generated using SDK credentials to authenticate Meeting SDK | Still required and NOT deprecated |
Key Point: The deprecation of JWT App Type does NOT affect Meeting SDK. You still need JWT signatures for SDK authentication.
A JWT (JSON Web Token) signature authenticates your application to use the Meeting SDK. Generated server-side using your SDK Client ID and Client Secret.
Always. Every Meeting SDK join requires a JWT signature.
// Server-side (Node.js)
const KJUR = require('jsrsasign');
function generateSignature(sdkKey, sdkSecret, meetingNumber, role) {
const iat = Math.round(Date.now() / 1000) - 30; // 30 seconds ago
const exp = iat + 60 * 60 * 2; // 2 hours from iat
const payload = {
sdkKey: sdkKey, // Your SDK Client ID
mn: meetingNumber, // Meeting number to join
role: role, // 0 = participant, 1 = host
iat: iat,
exp: exp,
tokenExp: exp
};
const header = { alg: 'HS256', typ: 'JWT' };
return KJUR.jws.JWS.sign('HS256',
JSON.stringify(header),
JSON.stringify(payload),
sdkSecret // Your SDK Client Secret
);
}// Generate token just before joining
const iat = Math.floor(Date.now() / 1000) - 7200; // 2 hours in past
const exp = Math.floor(Date.now() / 1000) + 10; // 10 seconds from now
// Why this works:
// - exp is short-lived (security)
// - exp - iat >= 2 hours (Zoom requirement)
// - Token generated right before use| Role | Value | Description |
|---|---|---|
| Participant | 0 | Join as attendee |
| Host | 1 | Join as host (requires being meeting owner or having host key) |
A short-lived credential that proves your bot/app is authenticated as a specific Zoom user. Generated via the Zoom REST API.
| Scenario | ZAK Required? |
|---|---|
| Meeting has “Only Authenticated Users Can Join” enabled | Yes |
| Starting a meeting as the host (when host not present) | Yes (host’s ZAK) |
| Changing bot’s profile picture to match a user | Yes |
| Regular meeting join | No |
Step 1: Get OAuth Access Token
curl -X POST "https://zoom.us/oauth/token" \
-H "Authorization: Basic {BASE64(client_id:client_secret)}" \
-d "grant_type=authorization_code&code={auth_code}&redirect_uri={redirect_uri}"Step 2: Generate ZAK Token
curl -X GET "https://api.zoom.us/v2/users/me/token?type=zak&ttl=7200" \
-H "Authorization: Bearer {access_token}"Response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}user:read:zak// Web SDK
ZoomMtg.join({
signature: signature, // JWT signature (always required)
sdkKey: clientId,
meetingNumber: meetingNumber,
passWord: password,
userName: "Meeting Bot",
zak: zakToken, // ZAK token for authenticated join
success: (success) => console.log('Joined'),
error: (error) => console.error(error)
});Wrong: Thinking ZAK must be from a meeting participant.
Right: Any Zoom account’s ZAK satisfies “Only Authenticated Users” requirement. Create one service account (e.g., meeting-bot@company.com) for all your bots.
A new credential that ties your bot to a specific user who is present in the meeting. Required for external meetings starting February 23, 2026.
Zoom introduced OBF tokens for accountability and transparency. It makes clear that an SDK app belongs to a specific person in the meeting.
| Date | External Meeting Requirement |
|---|---|
| Before Feb 23, 2026 | ZAK or OBF (optional) |
| After Feb 23, 2026 | ZAK or OBF required |
| Aspect | ZAK Token | OBF Token |
|---|---|---|
| User presence required | No | Yes - user MUST be in meeting |
| Bot disconnection | Stays if token owner leaves | Immediately disconnected if user leaves |
| Meeting scope | Any meeting | Specific meeting ID only |
| Attribution | Generic authentication | Tied to specific attending user |
Step 1: Get OAuth Access Token (same as ZAK)
Step 2: Generate OBF Token
curl -X GET "https://api.zoom.us/v2/users/me/token?type=onbehalf&meeting_id={meeting_id}" \
-H "Authorization: Bearer {access_token}"Response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}user:read:token// Web SDK
ZoomMtg.join({
signature: signature, // JWT signature (always required)
sdkKey: clientId,
meetingNumber: meetingNumber,
passWord: password,
userName: "Meeting Bot",
obfToken: obfToken, // OBF token (NOT zak)
success: (success) => console.log('Joined'),
error: (error) => console.error(error)
});Important: zak and obfToken are mutually exclusive. Use only one.
If bot joins before authorizing user is in meeting:
// SDK v6.6.10+ returns specific error code
// MEETING_FAIL_AUTHORIZED_USER_NOT_INMEETING
async function joinWithRetry(joinOptions, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
await ZoomMtg.join(joinOptions);
return; // Success
} catch (error) {
if (error.code === 'MEETING_FAIL_AUTHORIZED_USER_NOT_INMEETING') {
console.log(`User not in meeting yet. Retry ${i + 1}/${maxRetries}`);
await sleep(3000); // Wait 3 seconds
} else {
throw error; // Different error, don't retry
}
}
}
throw new Error('Max retries exceeded - user never joined meeting');
}You must map users to their meetings:
GET /users/{userId}/meetings// 1. Generate JWT signature (always required)
const signature = await generateSignature(sdkKey, sdkSecret, meetingNumber, 0);
// 2. Optionally get ZAK for authenticated-only meetings
let zakToken = null;
if (meetingRequiresAuth) {
zakToken = await getZAKToken(accessToken);
}
// 3. Join meeting
await ZoomMtg.join({
signature: signature,
sdkKey: sdkKey,
meetingNumber: meetingNumber,
passWord: password,
userName: "Meeting Bot",
zak: zakToken, // Optional
});// 1. Generate JWT signature (always required)
const signature = await generateSignature(sdkKey, sdkSecret, meetingNumber, 0);
// 2. For external meetings, get OBF token
const obfToken = await getOBFToken(accessToken, meetingNumber);
// 3. Wait for authorizing user to join (if using OBF)
// ... implement retry logic ...
// 4. Join meeting
await ZoomMtg.join({
signature: signature,
sdkKey: sdkKey,
meetingNumber: meetingNumber,
passWord: password,
userName: "Meeting Bot",
obfToken: obfToken, // For external meetings
});For headless Linux bots, use the official sample:
# Clone sample repository
git clone git@github.com:zoom/meetingsdk-headless-linux-sample.git
# Configure (sample.config.toml)
[credentials]
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
[meeting]
join_url = "https://zoom.us/j/123456789?pwd=xxx"
# OR
meeting_id = 123456789
password = "abc123"
# Optional tokens
zak_token = "..." # For authenticated joins
obf_token = "..." # For external meetings
# Run with Docker
docker compose up| Mistake | Reality | Fix |
|---|---|---|
| JWT signatures are deprecated | Only JWT App Type is deprecated | Continue using JWT signatures for SDK |
| ZAK must be from meeting participant | Any Zoom account’s ZAK works | Use single service account |
| Using both ZAK and OBF together | They’re mutually exclusive | Use only one |
| Generating OBF before user in meeting | OBF requires user presence | Implement retry logic |
| Development credentials for external meetings | Dev credentials only work for your account | Get production credentials (4-6 week review) |
| Date | Change |
|---|---|
| Now | JWT signatures required; ZAK optional |
| Nov 2025 | SDK v6.6.10 with OBF-specific error codes |
| Feb 23, 2026 | OBF or ZAK required for external meetings |
| Token | Required Scope |
|---|---|
| ZAK Token | user:read:zak |
| OBF Token | user:read:token |