HEX
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.4.25
System: Windows NT DESKTOP-4TAV2RJ 10.0 build 19045 (Windows 10) AMD64
User: fred (0)
PHP: 8.4.25
Disabled: NONE
Upload Files
File: C:/Users/fred/.codex/.tmp/plugins/plugins/zoom/skills/meeting-sdk/references/bot-authentication.md
# Bot Authentication - JWT, ZAK, and OBF Tokens

Understanding the different token types for Meeting SDK authentication, especially for bots joining meetings.

## Overview

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 Types Summary

| 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** |

## Common Confusion

### JWT App Type vs JWT Signature

**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.

---

## 1. JWT Signature (Always Required)

### What Is It?

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.

### When to Use

**Always.** Every Meeting SDK join requires a JWT signature.

### How to Generate

```javascript
// 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
  );
}
```

### Best Practice: Short-Lived Tokens

```javascript
// 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 Values

| Role | Value | Description |
|------|-------|-------------|
| Participant | 0 | Join as attendee |
| Host | 1 | Join as host (requires being meeting owner or having host key) |

---

## 2. ZAK Token (Zoom Access Key)

### What Is It?

A short-lived credential that proves your bot/app is authenticated as a specific Zoom user. Generated via the Zoom REST API.

### When to Use

| 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 |

### How to Get ZAK Token

**Step 1: Get OAuth Access Token**

```bash
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**

```bash
curl -X GET "https://api.zoom.us/v2/users/me/token?type=zak&ttl=7200" \
  -H "Authorization: Bearer {access_token}"
```

**Response:**
```json
{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
```

### Required OAuth Scope

```
user:read:zak
```

### Using ZAK in SDK Join

```javascript
// 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)
});
```

### Key Properties

- **Short-lived**: Configurable TTL (typically 1-2 hours)
- **Any ZAK works**: Doesn't need to be from a meeting participant
- **No concurrency limit**: One service account can generate unlimited tokens
- **Expiry checked at join**: If already in meeting, bot stays connected even if ZAK expires

### Common Mistake

**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., `[email protected]`) for all your bots.

---

## 3. OBF Token (On-Behalf-Of Token)

### What Is It?

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**.

### Why Introduced?

Zoom introduced OBF tokens for accountability and transparency. It makes clear that an SDK app belongs to a specific person in the meeting.

### When to Use

| Date | External Meeting Requirement |
|------|------------------------------|
| Before Feb 23, 2026 | ZAK or OBF (optional) |
| **After Feb 23, 2026** | **ZAK or OBF required** |

### Critical Difference: OBF vs ZAK

| 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 |

### How to Get OBF Token

**Step 1: Get OAuth Access Token** (same as ZAK)

**Step 2: Generate OBF Token**

```bash
curl -X GET "https://api.zoom.us/v2/users/me/token?type=onbehalf&meeting_id={meeting_id}" \
  -H "Authorization: Bearer {access_token}"
```

**Response:**
```json
{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
```

### Required OAuth Scope

```
user:read:token
```

### Using OBF in SDK Join

```javascript
// 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.

### Handling Join Failures

If bot joins before authorizing user is in meeting:

```javascript
// 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');
}
```

### OBF Token Mapping

You must map users to their meetings:

1. **Zoom Meetings API**: `GET /users/{userId}/meetings`
2. **Calendar integration**: Parse meeting invites from Google Calendar/Outlook

---

## Complete Bot Join Flow

### Current (Pre-Feb 2026)

```javascript
// 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
});
```

### Post-Feb 2026 (External Meetings)

```javascript
// 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
});
```

---

## Linux Bot Implementation

For headless Linux bots, use the official sample:

```bash
# Clone sample repository
git clone [email protected]: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
```

---

## Common Mistakes

| 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) |

---

## Timeline

| 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** |

---

## OAuth Scopes Summary

| Token | Required Scope |
|-------|----------------|
| ZAK Token | `user:read:zak` |
| OBF Token | `user:read:token` |

## Resources

- **Meeting SDK Auth**: https://developers.zoom.us/docs/meeting-sdk/auth/
- **OBF Token Announcement**: https://developers.zoom.us/blog/transition-to-obf-token-meetingsdk-apps/
- **Linux SDK Sample**: https://github.com/zoom/meetingsdk-headless-linux-sample
- **Developer Forum**: https://devforum.zoom.us/