
Zoom Skills
- 41 installs
- 64 repo stars
- Updated July 28, 2026
- zoom/skills
Implementation guidance for the Zoom Cobrowse SDK for web: real-time collaborative browsing between agents and customers with annotation and privacy masking.
About
Covers implementing collaborative browsing where support agents view and interact with a customer's browser session, with privacy controls and annotation tools. A developer uses it to add agent-assist cobrowsing to a web app.
- Real-time agent-customer collaborative browsing via PIN-based sessions
- Privacy masking, annotation tools, and remote assist
Zoom Skills by the numbers
- 41 all-time installs (skills.sh)
- Ranked #1,367 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zoom/skills --skill zoom-skillsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 41 |
|---|---|
| repo stars | ★ 64 |
| Last updated | July 28, 2026 |
| Repository | zoom/skills ↗ |
What it does
Implementation guidance for the Zoom Cobrowse SDK for web: real-time collaborative browsing between agents and customers with annotation and privacy masking.
Files
Zoom Cobrowse SDK - Web Development
Expert guidance for implementing collaborative browsing with the Zoom Cobrowse SDK. This SDK enables support agents to view and interact with a customer's browser session in real-time, with privacy controls and annotation tools.
Official Documentation: https://developers.zoom.us/docs/cobrowse-sdk/ API Reference: https://marketplacefront.zoom.us/sdk/cobrowse/ Quickstart Repository: https://github.com/zoom/CobrowseSDK-Quickstart Auth Endpoint Sample: https://github.com/zoom/cobrowsesdk-auth-endpoint-sample
Quick Links
New to Cobrowse SDK? Follow this path:
1. [Get Started Guide](get-started.md) - Complete setup from credentials to first session 2. [Session Lifecycle](concepts/session-lifecycle.md) - Understanding customer and agent flows 3. [JWT Authentication](concepts/jwt-authentication.md) - Token generation and security 4. [Customer Integration](examples/customer-integration.md) - Integrate SDK into your website 5. [Agent Integration](examples/agent-integration.md) - Set up agent portal (iframe or npm)
Core Concepts:
- [Two Roles Pattern](concepts/two-roles-pattern.md) - Customer vs Agent architecture
- [Session Lifecycle](concepts/session-lifecycle.md) - PIN generation, connection, reconnection
- [JWT Authentication](concepts/jwt-authentication.md) - SDK Key vs API Key, role_type, claims
- [Distribution Methods](concepts/distribution-methods.md) - CDN vs npm (BYOP)
Features:
- [Annotation Tools](examples/annotations.md) - Drawing, highlighting, pointer tools
- [Privacy Masking](examples/privacy-masking.md) - Hide sensitive fields from agents
- [Remote Assist](examples/remote-assist.md) - Agent can scroll customer's page
- [Multi-Tab Persistence](examples/multi-tab-persistence.md) - Session continues across tabs
- [BYOP Mode](examples/byop-custom-pin.md) - Bring Your Own PIN with npm integration
Troubleshooting:
- [Common Issues](troubleshooting/common-issues.md) - Quick diagnostics and solutions
- [Error Codes](troubleshooting/error-codes.md) - Complete error reference
- [CORS and CSP](troubleshooting/cors-csp.md) - Cross-origin and security policy configuration
- [Browser Compatibility](troubleshooting/browser-compatibility.md) - Supported browsers and limitations
- [5-Minute Runbook](RUNBOOK.md) - Fast preflight checks before deep debugging
Reference:
- [API Reference](references/api-reference.md) - Complete SDK methods and events
- [Settings Reference](references/settings-reference.md) - All initialization settings
- Integrated Index - see the section below in this file
SDK Overview
The Zoom Cobrowse SDK is a JavaScript library that provides:
- Real-Time Co-Browsing: Agent sees customer's browser activity live
- PIN-Based Sessions: Secure 6-digit PIN for customer-to-agent connection
- Annotation Tools: Drawing, highlighting, vanishing pen, rectangle, color picker
- Privacy Masking: CSS selector-based masking of sensitive form fields
- Remote Assist: Agent can scroll customer's page (with consent)
- Multi-Tab Persistence: Session continues when customer opens new tabs
- Auto-Reconnection: Session recovers from page refresh (2-minute window)
- Session Events: Real-time events for session state changes
- HTTPS Required: Secure connections (HTTP only works on loopback/local development hosts)
- No Plugins: Pure JavaScript, no browser extensions needed
Two Roles Architecture
Cobrowse has two distinct roles, each with different integration patterns:
| Role | role_type | Integration | JWT Required | Purpose |
|---|---|---|---|---|
| Customer | 1 | Website integration (CDN or npm) | Yes | User who shares their browser session |
| Agent | 2 | Iframe (CDN) or npm (BYOP only) | Yes | Support staff who views/assists customer |
Key Insight: Customer and agent use different integration methods but the same JWT authentication pattern.
Read This First (Critical)
For customer/agent demos, treat the PIN from customer SDK event pincode_updated as the only user-facing PIN.
- Show one clearly labeled value in UI (for example, Support PIN).
- Use that same PIN for agent join.
- Do not expose provisional/debug PINs from backend pre-start records to users.
If these rules are ignored, agent desk often fails with Pincode is not found / code 30308.
Typical Production Flow (Most Common)
This is the flow most teams implement first, and what users usually expect in demos:
1. Customer starts session first (role_type=1)
- Backend creates/records session
- Backend returns customer JWT
- Customer SDK starts and receives a PIN
2. Agent joins second (role_type=2)
- Agent enters customer PIN
- Backend validates PIN and session state
- Backend returns agent JWT
- Agent opens Zoom-hosted desk iframe (or custom npm agent UI in BYOP)
If a demo only has one generic "session" user, it is incomplete for real cobrowse operations.
Prerequisites
Platform Requirements
- Supported Browsers:
- Chrome 80+ ✓
- Firefox 78+ ✓
- Safari 14+ ✓
- Edge 80+ ✓
- Internet Explorer ✗ (not supported)
- Network Requirements:
- HTTPS required (HTTP works on loopback/local development hosts only)
- Allow cross-origin requests to
*.zoom.us - CSP headers must allow Zoom domains (see CORS and CSP guide)
- Third-Party Cookies:
- Must enable third-party cookies for refresh reconnection
- Privacy mode may limit certain features
Zoom Account Requirements
1. Zoom Workplace Account with SDK Universal Credit 2. Video SDK App created in Zoom Marketplace 3. Cobrowse SDK Credentials from the app's Cobrowse tab
Note: Cobrowse SDK is a feature of Video SDK (not a separate product).
Credentials Overview
You'll receive 4 credentials from Zoom Marketplace → Video SDK App → Cobrowse tab:
| Credential | Type | Used For | Exposure Safe? |
|---|---|---|---|
| SDK Key | Public | CDN URL, JWT app_key claim | ✓ Yes (client-side) |
| SDK Secret | Private | Sign JWTs | ✗ No (server-side only) |
| API Key | Private | REST API calls (optional) | ✗ No (server-side only) |
| API Secret | Private | REST API calls (optional) | ✗ No (server-side only) |
Critical: SDK Key is public (embedded in CDN URL), but SDK Secret must never be exposed client-side.
Quick Start
Step 1: Get SDK Credentials
1. Go to Zoom Marketplace 2. Open your Video SDK App (or create one) 3. Navigate to the Cobrowse tab 4. Copy your credentials:
- SDK Key
- SDK Secret
- API Key (optional)
- API Secret (optional)
Step 2: Set Up Token Server
Deploy a server-side endpoint to generate JWTs. Use the official sample:
git clone https://github.com/zoom/cobrowsesdk-auth-endpoint-sample.git
cd cobrowsesdk-auth-endpoint-sample
npm install
# Create .env file
cat > .env << EOF
ZOOM_SDK_KEY=your_sdk_key_here
ZOOM_SDK_SECRET=your_sdk_secret_here
PORT=4000
EOF
npm startToken endpoint:
// POST https://YOUR_TOKEN_SERVICE_BASE_URL
{
"role": 1, // 1 = customer, 2 = agent
"userId": "user123",
"userName": "John Doe"
}
// Response
{
"token": "eyJhbGciOiJIUzI1NiIs..."
}Step 3: Customer Side Integration (CDN)
<!DOCTYPE html>
<html>
<head>
<title>Customer - Cobrowse Demo</title>
<script type="module">
const ZOOM_SDK_KEY = 'YOUR_SDK_KEY';
// Load SDK from CDN
(function(r, a, b, f, c, d) {
r[f] = r[f] || { init: function() { r.ZoomCobrowseSDKInitArgs = arguments }};
var fragment = a.createDocumentFragment();
function loadJs(url) {
c = a.createElement(b);
d = a.getElementsByTagName(b)[0];
c["async"] = false;
c.src = url;
fragment.appendChild(c);
}
loadJs(`https://us01-zcb.zoom.us/static/resource/sdk/${ZOOM_SDK_KEY}/js/2.13.2`);
d.parentNode.insertBefore(fragment, d);
})(window, document, "script", "ZoomCobrowseSDK");
</script>
</head>
<body>
<h1>Customer Support</h1>
<button id="cobrowse-btn" disabled>Loading...</button>
<!-- Sensitive fields - will be masked from agent -->
<label>SSN: <input type="text" class="pii-mask" placeholder="XXX-XX-XXXX"></label>
<label>Credit Card: <input type="text" class="pii-mask" placeholder="XXXX-XXXX-XXXX-XXXX"></label>
<script type="module">
let sessionRef = null;
const settings = {
allowAgentAnnotation: true,
allowCustomerAnnotation: true,
piiMask: {
maskCssSelectors: ".pii-mask",
maskType: "custom_input"
}
};
ZoomCobrowseSDK.init(settings, function({ success, session, error }) {
if (success) {
sessionRef = session;
// Listen for PIN code
session.on("pincode_updated", (payload) => {
console.log("PIN Code:", payload.pincode);
// IMPORTANT: this is the PIN agent should use
alert(`Share this PIN with agent: ${payload.pincode}`);
});
// Listen for session events
session.on("session_started", () => console.log("Session started"));
session.on("agent_joined", () => console.log("Agent joined"));
session.on("agent_left", () => console.log("Agent left"));
session.on("session_ended", () => console.log("Session ended"));
document.getElementById("cobrowse-btn").disabled = false;
document.getElementById("cobrowse-btn").innerText = "Start Cobrowse Session";
} else {
console.error("SDK init failed:", error);
}
});
document.getElementById("cobrowse-btn").addEventListener("click", async () => {
// Fetch JWT from your server
const response = await fetch("https://YOUR_TOKEN_SERVICE_BASE_URL", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
role: 1,
userId: "customer_" + Date.now(),
userName: "Customer"
})
});
const { token } = await response.json();
// Start cobrowse session
sessionRef.start({ sdkToken: token });
});
</script>
</body>
</html>Step 4: Agent Side Integration (Iframe)
<!DOCTYPE html>
<html>
<head>
<title>Agent Portal</title>
</head>
<body>
<h1>Agent Portal</h1>
<iframe
id="agent-iframe"
width="1024"
height="768"
allow="autoplay *; camera *; microphone *; display-capture *; geolocation *;"
></iframe>
<script>
async function connectAgent() {
// Fetch JWT from your server
const response = await fetch("https://YOUR_TOKEN_SERVICE_BASE_URL", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
role: 2,
userId: "agent_" + Date.now(),
userName: "Support Agent"
})
});
const { token } = await response.json();
// Load Zoom agent portal
const iframe = document.getElementById("agent-iframe");
iframe.src = `https://us01-zcb.zoom.us/sdkapi/zcb/frame-templates/desk?access_token=${token}`;
}
connectAgent();
</script>
</body>
</html>Step 5: Test the Integration
1. Open two separate browsers (or incognito + normal) 2. Customer browser: Open customer page, click "Start Cobrowse Session" 3. Customer browser: Note the 6-digit PIN displayed 4. Agent browser: Open agent page, enter the PIN code 5. Both browsers: Session connects, agent can see customer's page 6. Test features: Annotations, data masking, remote assist
Key Features
1. Annotation Tools
Both customer and agent can draw on the shared screen:
const settings = {
allowAgentAnnotation: true, // Agent can draw
allowCustomerAnnotation: true // Customer can draw
};Available tools:
- Pen (persistent)
- Vanishing pen (disappears after 4 seconds)
- Rectangle
- Color picker
- Eraser
- Undo/Redo
2. Privacy Masking
Hide sensitive fields from agents using CSS selectors:
const settings = {
piiMask: {
maskType: "custom_input", // Mask specific fields
maskCssSelectors: ".pii-mask, #ssn", // CSS selectors
maskHTMLAttributes: "data-sensitive=true" // HTML attributes
}
};Supported masking:
- Text nodes ✓
- Form inputs ✓
- Select elements ✓
- Images ✗ (not supported)
- Links ✗ (not supported)
3. Remote Assist
Agent can scroll the customer's page:
const settings = {
remoteAssist: {
enable: true,
enableCustomerConsent: true, // Customer must approve
remoteAssistTypes: ['scroll_page'], // Only scroll supported
requireStopConfirmation: false // Confirmation when stopping
}
};4. Multi-Tab Session Persistence
Session continues when customer opens new tabs:
const settings = {
multiTabSessionPersistence: {
enable: true,
stateCookieKey: '$$ZCB_SESSION$$' // Cookie key (base64 encoded)
}
};Session Lifecycle
Customer Flow
1. Load SDK → CDN script loads ZoomCobrowseSDK 2. Initialize → ZoomCobrowseSDK.init(settings, callback) 3. Fetch JWT → Request token from your server (role_type=1) 4. Start Session → session.start({ sdkToken }) 5. PIN Generated → pincode_updated event fires 6. Share PIN → Customer gives 6-digit PIN to agent 7. Agent Joins → agent_joined event fires 8. Session Active → Real-time synchronization begins 9. End Session → session.end() or agent leaves
Agent Flow
1. Fetch JWT → Request token from your server (role_type=2) 2. Load Iframe → Point to Zoom agent portal with token 3. Enter PIN → Agent inputs customer's 6-digit PIN 4. Connect → session_joined event fires 5. View Session → Agent sees customer's browser 6. Use Tools → Annotations, remote assist, zoom 7. Leave Session → Click "Leave Cobrowse" button
Session Recovery (Auto-Reconnect)
When customer refreshes the page:
ZoomCobrowseSDK.init(settings, function({ success, session, error }) {
if (success) {
const sessionInfo = session.getSessionInfo();
// Check if session is recoverable
if (sessionInfo.sessionStatus === 'session_recoverable') {
session.join(); // Auto-rejoin previous session
} else {
// Start new session
session.start({ sdkToken });
}
}
});Recovery window: 2 minutes. After 2 minutes, session ends.
Critical Gotchas and Best Practices
⚠️ CRITICAL: SDK Secret Must Stay Server-Side
Problem: Developers often accidentally embed SDK Secret in frontend code.
Solution:
- ✓ SDK Key → Safe to expose (embedded in CDN URL)
- ✗ SDK Secret → Never expose (use for JWT signing server-side)
// ❌ WRONG - Secret exposed in frontend
const jwt = signJWT(payload, 'YOUR_SDK_SECRET'); // Security risk!
// ✅ CORRECT - Secret stays on server
const response = await fetch('/api/token', {
method: 'POST',
body: JSON.stringify({ role: 1, userId, userName })
});
const { token } = await response.json();SDK Key vs API Key (Different Purposes!)
| Credential | Used For | JWT Claim |
|---|---|---|
| SDK Key | CDN URL, JWT app_key | app_key: "SDK_KEY" |
| API Key | REST API calls (optional) | Not used in JWT |
Common mistake: Using API Key instead of SDK Key in JWT app_key claim.
Session Limits
| Limit | Value | What Happens |
|---|---|---|
| Customers per session | 1 | Error 1012: SESSION_CUSTOMER_COUNT_LIMIT |
| Agents per session | 5 | Error 1013: SESSION_AGENT_COUNT_LIMIT |
| Active sessions per browser | 1 | Error 1004: SESSION_COUNT_LIMIT |
| PIN code length | 10 chars max | Error 1008: SESSION_PIN_INVALID_FORMAT |
Session Timeout Behavior
| Event | Timeout | What Happens |
|---|---|---|
| Agent waiting for customer | 3 minutes | Session ends automatically |
| Page refresh reconnection | 2 minutes | Session ends if not reconnected |
| Reconnection attempts | 2 times max | Session ends after 2 failed attempts |
HTTPS Requirement
Problem: SDK doesn't load on HTTP sites.
Solution:
- Production: Use HTTPS ✓
- Development: Use a loopback host for local HTTP testing ✓
- Development: Use a local HTTPS endpoint with a trusted/self-signed cert if required ✓
Third-Party Cookies Required
Problem: Refresh reconnection doesn't work.
Solution: Enable third-party cookies in browser settings.
Affected scenarios:
- Browser privacy mode
- Safari with "Prevent cross-site tracking" enabled
- Chrome with "Block third-party cookies" enabled
Distribution Method Confusion
| Method | Use Case | Agent Integration | BYOP Required |
|---|---|---|---|
| CDN | Most use cases | Zoom-hosted iframe | No (auto PIN) |
| npm | Custom agent UI, full control | Custom npm integration | Yes (required) |
Key Insight: If you want npm integration, you must use BYOP (Bring Your Own PIN) mode.
Cross-Origin Iframe Handling
Problem: Cobrowse doesn't work in cross-origin iframes.
Solution: Inject SDK snippet into cross-origin iframes:
<script>
const ZOOM_SDK_KEY = "YOUR_SDK_KEY_HERE";
(function(r,a,b,f,c,d){r[f]=r[f]||{init:function(){r.ZoomCobrowseSDKInitArgs=arguments}};
var fragment=a.createDocumentFragment();function loadJs(url) {c=a.createElement(b);d=a.getElementsByTagName(b)[0];c.async=false;c.src=url;fragment.appendChild(c);};
loadJs('https://us01-zcb.zoom.us/static/resource/sdk/${ZOOM_SDK_KEY}/js');d.parentNode.insertBefore(fragment,d);})(window,document,'script','ZoomCobrowseSDK');
</script>Same-origin iframes: No extra setup needed.
Known Limitations
Synchronization Limits
Not synchronized:
- HTML5 Canvas elements
- WebGL content
- Audio and Video elements
- Shadow DOM
- PDF rendered with Canvas
- Web Components
Partially synchronized:
- Drop-down boxes (only selected result)
- Date pickers (only selected result)
- Color pickers (only selected result)
Rendering Limits
- High-resolution images may be compressed
- Different screen sizes may cause CSS media query differences
- Cross-origin images may not render (CORS restrictions)
- Cross-origin fonts may not render (CORS restrictions)
Masking Limits
Supported:
- Text nodes ✓
- Form inputs ✓
- Select elements ✓
Not supported:
<img>elements ✗- Links ✗
Complete Documentation Library
This skill includes comprehensive guides organized by category:
Core Concepts
- [Two Roles Pattern](concepts/two-roles-pattern.md) - Customer vs Agent architecture
- [Session Lifecycle](concepts/session-lifecycle.md) - Complete flow from start to end
- [JWT Authentication](concepts/jwt-authentication.md) - Token structure and signing
- [Distribution Methods](concepts/distribution-methods.md) - CDN vs npm (BYOP)
Examples
- [Customer Integration](examples/customer-integration.md) - Complete customer-side setup
- [Agent Integration](examples/agent-integration.md) - Iframe and npm agent setups
- [Annotations](examples/annotations.md) - Drawing tools configuration
- [Privacy Masking](examples/privacy-masking.md) - Field masking patterns
- [Remote Assist](examples/remote-assist.md) - Agent page control
- [Multi-Tab Persistence](examples/multi-tab-persistence.md) - Cross-tab sessions
- [BYOP Custom PIN](examples/byop-custom-pin.md) - Custom PIN codes
References
- [API Reference](references/api-reference.md) - Complete SDK methods and events
- [Settings Reference](references/settings-reference.md) - All initialization settings
- [Error Codes](references/error-codes.md) - Complete error reference
- [Session Events](references/session-events.md) - All event types
Troubleshooting
- [Common Issues](troubleshooting/common-issues.md) - Quick diagnostics
- [Error Codes](troubleshooting/error-codes.md) - Error code reference
- [CORS and CSP](troubleshooting/cors-csp.md) - Cross-origin configuration
- [Browser Compatibility](troubleshooting/browser-compatibility.md) - Browser support
Resources
- Official Docs: https://developers.zoom.us/docs/cobrowse-sdk/
- API Reference: https://marketplacefront.zoom.us/sdk/cobrowse/
- Quickstart Repo: https://github.com/zoom/CobrowseSDK-Quickstart
- Auth Endpoint Sample: https://github.com/zoom/cobrowsesdk-auth-endpoint-sample
- Dev Forum: https://devforum.zoom.us/
- Developer Blog: https://developers.zoom.us/blog/?category=zoom-cobrowse-sdk
---
Need help? Start with Integrated Index section below for complete navigation.
---
Integrated Index
_This section was migrated from SKILL.md._
Complete navigation guide for all Cobrowse SDK documentation.
Getting Started (Start Here!)
If you're new to Zoom Cobrowse SDK, follow this learning path:
1. [SKILL.md](SKILL.md) - Main overview and quick start 2. [5-Minute Runbook](RUNBOOK.md) - Preflight checks for common failures 3. [Get Started Guide](get-started.md) - Step-by-step setup from credentials to first session 4. [Session Lifecycle](concepts/session-lifecycle.md) - Understand the complete customer and agent flow 5. [Customer Integration](examples/customer-integration.md) - Integrate SDK into your website 6. [Agent Integration](examples/agent-integration.md) - Set up agent portal
Core Concepts
Foundational concepts you need to understand:
- [Two Roles Pattern](concepts/two-roles-pattern.md) - Customer (role_type=1) vs Agent (role_type=2) architecture
- [Session Lifecycle](concepts/session-lifecycle.md) - Complete flow: init → start → PIN → connect → end
- [JWT Authentication](concepts/jwt-authentication.md) - Token structure, signing, SDK Key vs API Key
- [Distribution Methods](concepts/distribution-methods.md) - CDN vs npm (BYOP mode)
Examples and Patterns
Complete working examples for common scenarios:
Session Management
- [Customer Integration](examples/customer-integration.md) - Complete customer-side implementation (CDN and npm)
- [Agent Integration](examples/agent-integration.md) - Iframe and npm agent setup patterns
- [Session Events](examples/session-events.md) - Handle all session lifecycle events
- [Auto-Reconnection](examples/auto-reconnection.md) - Page refresh and session recovery
Features
- [Annotation Tools](examples/annotations.md) - Enable drawing, highlighting, vanishing pen
- [Privacy Masking](examples/privacy-masking.md) - Mask sensitive fields with CSS selectors
- [Remote Assist](examples/remote-assist.md) - Agent can scroll customer's page
- [Multi-Tab Persistence](examples/multi-tab-persistence.md) - Session continues across browser tabs
- [BYOP Custom PIN](examples/byop-custom-pin.md) - Bring Your Own PIN with npm integration
References
Complete API and configuration references:
SDK Reference
- [API Reference](references/api-reference.md) - All SDK methods and interfaces
- ZoomCobrowseSDK.init()
- session.start()
- session.join()
- session.end()
- session.on()
- session.getSessionInfo()
- [Settings Reference](references/settings-reference.md) - All initialization settings
- allowAgentAnnotation
- allowCustomerAnnotation
- piiMask
- remoteAssist
- multiTabSessionPersistence
- [Session Events Reference](references/session-events.md) - All event types
- pincode_updated
- session_started
- session_ended
- agent_joined
- agent_left
- session_error
- session_reconnecting
- remote_assist_started
- remote_assist_stopped
Error Reference
- [Error Codes](references/error-codes.md) - Complete error code reference
- 1001-1017: Session errors
- 2001: Token errors
- 9999: Service errors
Official Documentation
- [Get Started](references/get-started.md) - Official get started documentation (crawled)
- [Features](references/features.md) - Official features documentation (crawled)
- [Authorization](references/authorization.md) - Official JWT authorization docs (crawled)
- [API Documentation](references/api.md) - Crawled API reference docs
Troubleshooting
Quick diagnostics and common issue resolution:
- [Common Issues](troubleshooting/common-issues.md) - Quick fixes for frequent problems
- SDK not loading
- Token generation fails
- Agent can't connect
- Fields not masked
- Session doesn't reconnect after refresh
- [Error Codes](troubleshooting/error-codes.md) - Error code lookup and solutions
- Session start/join failures (1001, 1011, 1016)
- Session limit errors (1002, 1004, 1012, 1013, 1015)
- PIN code errors (1006, 1008, 1009, 1010)
- Token errors (2001)
- [CORS and CSP](troubleshooting/cors-csp.md) - Cross-origin and Content Security Policy setup
- Access-Control-Allow-Origin headers
- Content-Security-Policy headers
- Cross-origin iframe handling
- Same-origin iframe handling
- [Browser Compatibility](troubleshooting/browser-compatibility.md) - Browser requirements and limitations
- Supported browsers (Chrome 80+, Firefox 78+, Safari 14+, Edge 80+)
- Internet Explorer not supported
- Privacy mode limitations
- Third-party cookie requirements
By Use Case
Find documentation by what you're trying to do:
I want to...
Set up cobrowse for the first time:
- Get Started Guide
- JWT Authentication
- Customer Integration
- Agent Integration
Add annotation tools:
- Annotation Tools Example
- [Settings Reference - allowAgentAnnotation](references/settings-reference.md#allowa gentannotation)
- Settings Reference - allowCustomerAnnotation
Hide sensitive data from agents:
- Privacy Masking Example
- Settings Reference - piiMask
Let agents control customer's page:
- Remote Assist Example
- Settings Reference - remoteAssist
Use custom PIN codes:
- BYOP Custom PIN Example
- JWT Authentication - enable_byop
Handle page refreshes:
- Auto-Reconnection Example
- Session Lifecycle - Recovery
Integrate with npm (not CDN):
- BYOP Custom PIN Example
- Distribution Methods
Debug session connection issues:
- Common Issues
- Error Codes
- Session Events - session_error
Configure CORS and CSP headers:
- CORS and CSP Guide
- Browser Compatibility
By Error Code
Quick lookup for error code solutions:
Session Errors
- 1001 (SESSION_START_FAILED) → Error Codes
- 1002 (SESSION_CONNECTING_IN_PROGRESS) → Error Codes
- 1004 (SESSION_COUNT_LIMIT) → Error Codes
- 1011 (SESSION_JOIN_FAILED) → Error Codes
- 1012 (SESSION_CUSTOMER_COUNT_LIMIT) → Error Codes
- 1013 (SESSION_AGENT_COUNT_LIMIT) → Error Codes
- 1015 (SESSION_DUPLICATE_USER) → Error Codes
- 1016 (NETWORK_ERROR) → Error Codes
- 1017 (SESSION_CANCELING_IN_PROGRESS) → Error Codes
PIN Errors
- 1006 (SESSION_JOIN_PIN_NOT_FOUND) → Error Codes
- 1008 (SESSION_PIN_INVALID_FORMAT) → Error Codes
- 1009 (SESSION_START_PIN_REQUIRED) → Error Codes
- 1010 (SESSION_START_PIN_CONFLICT) → Error Codes
Auth Errors
- 2001 (TOKEN_INVALID) → Error Codes
Service Errors
- 9999 (UNDEFINED) → Error Codes
Official Resources
External documentation and samples:
- Official Docs: https://developers.zoom.us/docs/cobrowse-sdk/
- API Reference: https://marketplacefront.zoom.us/sdk/cobrowse/
- Quickstart Repo: https://github.com/zoom/CobrowseSDK-Quickstart
- Auth Endpoint Sample: https://github.com/zoom/cobrowsesdk-auth-endpoint-sample
- Dev Forum: https://devforum.zoom.us/
- Developer Blog: https://developers.zoom.us/blog/?category=zoom-cobrowse-sdk
Documentation Structure
cobrowse-sdk/
├── SKILL.md # Main skill entry point
├── SKILL.md # This file - complete navigation
├── get-started.md # Step-by-step setup guide
│
├── concepts/ # Core concepts
│ ├── two-roles-pattern.md
│ ├── session-lifecycle.md
│ ├── jwt-authentication.md
│ └── distribution-methods.md
│
├── examples/ # Working examples
│ ├── customer-integration.md
│ ├── agent-integration.md
│ ├── annotations.md
│ ├── privacy-masking.md
│ ├── remote-assist.md
│ ├── multi-tab-persistence.md
│ ├── byop-custom-pin.md
│ ├── session-events.md
│ └── auto-reconnection.md
│
├── references/ # API and config references
│ ├── api-reference.md # SDK methods
│ ├── settings-reference.md # Init settings
│ ├── session-events.md # Event types
│ ├── error-codes.md # Error reference
│ ├── get-started.md # Official docs (crawled)
│ ├── features.md # Official docs (crawled)
│ ├── authorization.md # Official docs (crawled)
│ └── api.md # API docs (crawled)
│
└── troubleshooting/ # Problem resolution
├── common-issues.md
├── error-codes.md
├── cors-csp.md
└── browser-compatibility.mdSearch Tips
Find by keyword:
- "annotation" → Annotation Tools
- "mask" or "privacy" → Privacy Masking
- "PIN" or "custom PIN" → BYOP Custom PIN
- "JWT" or "token" → JWT Authentication
- "error" → Error Codes
- "CORS" or "CSP" → CORS and CSP
- "iframe" → Agent Integration
- "npm" → Distribution Methods, BYOP
- "refresh" or "reconnect" → Auto-Reconnection
- "agent" → Agent Integration, Two Roles Pattern
- "customer" → Customer Integration, Two Roles Pattern
---
Not finding what you need? Check the Official Documentation or ask on the Dev Forum.
Environment Variables
- See references/environment-variables.md for standardized
.envkeys and where to find each value.
Distribution Methods
Zoom Cobrowse supports CDN and npm-based integrations, depending on your architecture.
Choose based on:
- how much UI control you need,
- whether you host your own agent experience,
- your deployment and CSP constraints.
See:
- Get Started
- Features (official)
JWT Authentication
Generate Cobrowse JWTs server-side using your SDK key and SDK secret.
Guidelines:
- Never expose SDK secret client-side.
- Issue short-lived tokens.
- Generate different tokens for customer and agent roles.
See:
- Authorization (official)
- Get Started
Session Lifecycle
Typical flow:
1. Initialize SDK on customer and agent pages. 2. Generate role-specific JWT tokens. 3. Customer starts a session and receives a PIN. 4. Agent joins using the PIN. 5. Session events track connected/disconnected/end states.
See:
- Get Started
- Features (official)
Two Roles Pattern
Zoom Cobrowse uses two roles:
role_type=1: customer sessionrole_type=2: agent session
Use separate JWTs for each role and keep token generation on the server.
What Is Usually Created
In most real implementations, you create these objects in order:
1. Customer session record (server-side)
session_id- generated PIN
- status (
active/revoked) - expiry timestamp
2. Customer token (role_type=1)
- used by customer browser SDK to start/share session
3. Agent token (role_type=2)
- created after PIN validation
- used to load agent desk iframe or custom agent UI
PIN Source of Truth
In practice, the PIN you should hand to agents is the value emitted by customer SDK event:
session.on("pincode_updated", ...)
Do not rely on placeholder/provisional PIN values from pre-start backend records for user-facing flows. Always show one clearly labeled PIN in UI (for example, "Support PIN") and reuse that same value in agent links.
Recommended Endpoint Split
POST /api/customer/start-> create session + customer token + PINPOST /api/agent/connect-> validate PIN + issue agent tokenPOST /api/session/revoke-> end sessionGET /api/session/list-> operational visibility
See:
- Get Started
- Authorization (official)
Agent Integration
Agent integration joins an active customer session by PIN using an agent-role token.
See:
- Get Started
- Authorization (official)
Annotation Tools
Enable annotation settings during SDK initialization to allow drawing and highlighting.
See:
- Features (official)
- API (official)
Auto-Reconnection
Implement reconnection handlers for transient network interruptions and refresh scenarios.
See:
- Get Started
- Features (official)
BYOP Custom PIN
Bring Your Own PIN mode lets you control PIN generation/format in your own application flow.
See:
- Authorization (official)
- Get Started
Customer Integration
Customer-side integration should initialize the SDK, fetch a server-generated token, then start a session.
See:
- Get Started
- API (official)
Multi-Tab Persistence
Cobrowse sessions can continue across tabs when configured correctly and browser constraints are met.
See:
- Features (official)
- Get Started
Privacy Masking
Configure masking selectors for sensitive customer fields so agents cannot view protected values.
See:
- Features (official)
- Get Started
Remote Assist
Remote assist allows approved agent interactions (for example, scrolling) during active sessions.
See:
- Features (official)
- API (official)
Session Events
Use SDK session events to track lifecycle transitions and update your UI accordingly.
See:
- API (official)
- Features (official)
Get Started with Zoom Cobrowse SDK
Complete setup guide from credentials to your first cobrowse session.
Overview
In a cobrowse session, there are two roles:
- Customer (role_type=1) – Integrates the SDK into their website
- Agent (role_type=2) – Uses an embedded iframe to interact with the customer
This guide shows you how to set up a customer-initiated session (the most common pattern).
Step 1: Get SDK Credentials
Requirements
1. Zoom Workplace Account with SDK Universal Credit
- See Build platform - create or update account for details
2. Video SDK App in Zoom Marketplace
- Cobrowse SDK is a feature of Video SDK (not a separate product)
Get Your Credentials
1. Access your SDK account web portal:
- In your Zoom Workplace account, go to Advanced > Zoom CPaaS > Manage
2. Click Build App
3. Locate your SDK credentials in the Cobrowse tab
You'll receive 4 credentials:
| Credential | Type | Purpose |
|---|---|---|
| SDK Key | Public | Used in CDN URL and JWT app_key claim |
| SDK Secret | Private | Used to sign JWTs (server-side only) |
| API Key | Private | REST API authentication (optional) |
| API Secret | Private | REST API authentication (optional) |
Save these credentials securely - you'll need them in the next step.
Step 2: Generate JWT Tokens
Both customers and agents require JSON Web Tokens (JWTs) for authentication.
JWT Structure
All JWTs have the same header:
{
"alg": "HS256",
"typ": "JWT"
}The payload differs by role:
Customer JWT payload (role_type=1):
{
"user_id": "user1_customer",
"app_key": "YOUR_SDK_KEY",
"role_type": 1,
"user_name": "customer",
"exp": 1723103759,
"iat": 1723102859
}Agent JWT payload (role_type=2):
{
"user_id": "user2_agent",
"app_key": "YOUR_SDK_KEY",
"role_type": 2,
"user_name": "agent",
"exp": 1723103759,
"iat": 1723102859
}JWT Payload Fields
| Field | Required | Description |
|---|---|---|
app_key | Yes | Your Zoom SDK Key (not API Key) |
role_type | Yes | User role: 1 = customer, 2 = agent |
iat | Yes | Token issue timestamp (epoch) |
exp | Yes | Token expiration timestamp (epoch). Min: 30 minutes, Max: 48 hours |
user_id | Yes | Uniquely identifiable user ID |
user_name | Yes | User name (max 80 characters) |
enable_byop | Optional | Enable Bring Your Own PIN: 1 = yes, 0 or omit = no |
Sign the JWT
Sign the JWT with your SDK Secret (not API Secret):
HMACSHA256(
base64UrlEncode(header) + '.' + base64UrlEncode(payload),
ZOOM_SDK_SECRET
);Set Up a Token Server
CRITICAL: JWT signing must happen server-side to protect your SDK Secret.
Use the official auth endpoint sample:
# Clone the sample
git clone https://github.com/zoom/cobrowsesdk-auth-endpoint-sample.git
cd cobrowsesdk-auth-endpoint-sample
# Install dependencies
npm install
# Create .env file
cat > .env << EOF
ZOOM_SDK_KEY=your_sdk_key_here
ZOOM_SDK_SECRET=your_sdk_secret_here
PORT=4000
EOF
# Start the server
npm startThe server will run on the base URL you configure for your token service.
Token Request:
// POST https://YOUR_TOKEN_SERVICE_BASE_URL
{
"role": 1, // 1 = customer, 2 = agent
"userId": "user123",
"userName": "John Doe"
}
// Response
{
"token": "eyJhbGciOiJIUzI1NiIs..."
}See also: JWT Authentication Concept
Step 3: Integrate the Customer SDK
The customer integrates the Cobrowse SDK into their website using the CDN.
Critical PIN Rule
>
The PIN agents should use comes from customer SDK event pincode_updated.Do not show or rely on provisional PIN values from backend/session placeholders.
In UI, display one explicit value (for example, Support PIN) and pass only that to agent flow.
Load the SDK
Include the SDK snippet in the <head> tag of your HTML page:
<script type="module">
const ZOOM_SDK_KEY = 'YOUR_SDK_KEY';
(function (r, a, b, f, c, d) {
r[f] = r[f] || {
init: function () {
r.ZoomCobrowseSDKInitArgs = arguments;
},
};
var fragment = a.createDocumentFragment();
function loadJs(url) {
c = a.createElement(b);
d = a.getElementsByTagName(b)[0];
c.async = false;
c.src = url;
fragment.appendChild(c);
}
loadJs(
`https://us01-zcb.zoom.us/static/resource/sdk/${ZOOM_SDK_KEY}/js/2.13.2`
);
d.parentNode.insertBefore(fragment, d);
})(window, document, 'script', 'ZoomCobrowseSDK');
</script>SDK Version
Set the SDK VERSION using semantic versioning:
- Fixed version:
js/2.13.2- Use exact version 2.13.2 - Latest patch:
js/2.13.x- Use latest>=2.13.0 and <2.14.0
Current version: 2.13.2 (as of February 2026)
Initialize the SDK
const settings = {
allowCustomerAnnotation: true,
piiMask: { maskType: 'all_input' },
};
ZoomCobrowseSDK.init(settings, function ({ success, session, error }) {
if (success) {
console.log("SDK initialized successfully");
// session object is now available
} else {
console.error("SDK init failed:", error);
}
});Start a Session
// Fetch JWT from your server
const response = await fetch('https://YOUR_TOKEN_SERVICE_BASE_URL', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
role: 1,
userId: 'customer_' + Date.now(),
userName: 'Customer'
})
});
const { token } = await response.json();
// Start cobrowse session
session.start({ sdkToken: token });Complete Customer Example
<!DOCTYPE html>
<html>
<head>
<title>Customer - Cobrowse Support</title>
<script type="module">
const ZOOM_SDK_KEY = 'YOUR_SDK_KEY';
// Load SDK from CDN
(function(r, a, b, f, c, d) {
r[f] = r[f] || { init: function() { r.ZoomCobrowseSDKInitArgs = arguments }};
var fragment = a.createDocumentFragment();
function loadJs(url) {
c = a.createElement(b);
d = a.getElementsByTagName(b)[0];
c["async"] = false;
c.src = url;
fragment.appendChild(c);
}
loadJs(`https://us01-zcb.zoom.us/static/resource/sdk/${ZOOM_SDK_KEY}/js/2.13.2`);
d.parentNode.insertBefore(fragment, d);
})(window, document, "script", "ZoomCobrowseSDK");
</script>
</head>
<body>
<h1>Need Help?</h1>
<button id="cobrowse-btn" disabled>Loading...</button>
<div id="pin-display"></div>
<script type="module">
let sessionRef = null;
const settings = {
allowAgentAnnotation: true,
allowCustomerAnnotation: true,
piiMask: {
maskType: "custom_input",
maskCssSelectors: ".sensitive-field"
}
};
ZoomCobrowseSDK.init(settings, function({ success, session, error }) {
if (success) {
sessionRef = session;
// Listen for PIN code
session.on("pincode_updated", (payload) => {
console.log("PIN Code:", payload.pincode);
// This is the authoritative PIN for agent join
document.getElementById("pin-display").innerHTML =
`<p><strong>Your PIN:</strong> ${payload.pincode}</p>
<p>Share this with your support agent</p>`;
});
// Enable button
document.getElementById("cobrowse-btn").disabled = false;
document.getElementById("cobrowse-btn").innerText = "Start Support Session";
} else {
console.error("SDK init failed:", error);
}
});
// Handle button click
document.getElementById("cobrowse-btn").addEventListener("click", async () => {
try {
// Fetch JWT from your server
const response = await fetch("https://YOUR_TOKEN_SERVICE_BASE_URL", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
role: 1,
userId: "customer_" + Date.now(),
userName: "Customer"
})
});
const { token } = await response.json();
// Start session
sessionRef.start({ sdkToken: token });
} catch (error) {
console.error("Failed to start session:", error);
}
});
</script>
</body>
</html>Step 4: Use Zoom-Hosted Agent Portal
Agents connect to cobrowse sessions by embedding an iframe.
Agent Portal Iframe
<!DOCTYPE html>
<html>
<head>
<title>Agent Portal</title>
</head>
<body>
<h1>Agent Support Portal</h1>
<iframe
id="agent-iframe"
width="1024"
height="768"
src=""
allow="autoplay *; camera *; microphone *; display-capture *; geolocation *;"
></iframe>
<script>
async function connectAgent() {
try {
// Fetch JWT from your server
const response = await fetch("https://YOUR_TOKEN_SERVICE_BASE_URL", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
role: 2,
userId: "agent_" + Date.now(),
userName: "Support Agent"
})
});
const { token } = await response.json();
// Load Zoom agent portal with token
const iframe = document.getElementById("agent-iframe");
iframe.src = `https://us01-zcb.zoom.us/sdkapi/zcb/frame-templates/desk?access_token=${token}`;
} catch (error) {
console.error("Failed to connect agent:", error);
}
}
// Auto-connect on page load
connectAgent();
</script>
</body>
</html>Iframe Permissions
The allow attribute must include these permissions:
autoplay *- Auto-play mediacamera *- Camera accessmicrophone *- Microphone accessdisplay-capture *- Screen capturegeolocation *- Location services
Step 5: Test the Cobrowse SDK
Testing Steps
1. Open two browsers (or use incognito + normal mode):
- Browser A: Customer page
- Browser B: Agent page
2. Customer browser:
- Open customer page
- Click "Start Support Session" button
- Note the 6-digit PIN displayed
3. Agent browser:
- Open agent page
- Enter the PIN code in the iframe
4. Verify connection:
- Agent should now see the customer's browser
- Both sides should show "Connected" status
5. Test features:
- Annotations: Agent can draw on the screen
- Data masking: Masked fields show asterisks for agent
- Remote assist: Agent can scroll the page (if enabled)
6. End session:
- Either side can click "End Session" to terminate
Troubleshooting Test Issues
| Issue | Solution |
|---|---|
| SDK doesn't load | Verify SDK Key is correct in CDN URL |
| PIN not showing | Check browser console for errors |
| Agent can't connect | Verify PIN is correct and session is still active |
| Connection fails | Check HTTPS is being used (or a loopback host for development) |
Step 6: Add Features
Now that you have a working cobrowse session, add features:
Annotation Tools
Enable drawing tools for customer and/or agent:
const settings = {
allowAgentAnnotation: true, // Agent can draw
allowCustomerAnnotation: true // Customer can draw
};See: Annotation Tools Example
Data Masking
Hide sensitive fields from agents:
const settings = {
piiMask: {
maskType: 'custom_input',
maskCssSelectors: '.sensitive-field, #ssn, #credit-card',
maskHTMLAttributes: 'data-sensitive=true'
}
};See: Privacy Masking Example
Remote Assist
Allow agent to scroll the customer's page:
const settings = {
remoteAssist: {
enable: true,
enableCustomerConsent: true, // Customer must approve
remoteAssistTypes: ['scroll_page']
}
};See: Remote Assist Example
Bring Your Own PIN (BYOP)
Use custom PIN codes instead of auto-generated ones:
1. Enable BYOP in JWT payload:
{
"enable_byop": 1,
...
}2. Provide custom PIN when starting session:
session.start({
customPinCode: 'MYPIN123',
sdkToken: token
});See: BYOP Custom PIN Example
Next Steps
- Learn core concepts: Session Lifecycle
- Explore features: Complete documentation index
- Handle errors: Error Codes Reference
- Production checklist: CORS and CSP Configuration
PIN Code Access - Bring Your Own PIN (BYOP)
The Cobrowse SDK supports connecting agents and customers using a PIN code. In the simple example above, Zoom automatically generates a 6-digit PIN code displayed to the customer.
Auto-generated PIN flow: 1. Customer clicks "Start Support Session" 2. Zoom generates 6-digit PIN 3. Customer shares PIN with agent 4. Agent enters PIN to connect
Custom PIN flow (BYOP): 1. Your app generates custom PIN code (1-10 characters, letters/numbers) 2. Pass PIN when starting session: session.start({ customPinCode: 'MYPIN', sdkToken }) 3. Agent enters your custom PIN to connect
BYOP enables:
- Integration with existing support ticket systems
- Use of case/ticket IDs as PINs
- npm integration for custom agent UI
See: Bring Your Own PIN (BYOP) for complete guide.
Resources
- Official Docs: https://developers.zoom.us/docs/cobrowse-sdk/
- API Reference: https://marketplacefront.zoom.us/sdk/cobrowse/
- Quickstart Repo: https://github.com/zoom/CobrowseSDK-Quickstart
- Auth Endpoint Sample: https://github.com/zoom/cobrowsesdk-auth-endpoint-sample
- Dev Forum: https://devforum.zoom.us/
Common Questions
Q: Can I use HTTP instead of HTTPS? A: Only for loopback/local development. Production must use HTTPS.
Q: What's the difference between SDK Key and API Key? A: SDK Key is used in the CDN URL and JWT app_key claim. API Key is for optional REST API calls.
Q: Can multiple agents join the same session? A: Yes, up to 5 agents can join a single customer session.
Q: Does the customer need to install anything? A: No, it's pure JavaScript delivered via CDN. No plugins or extensions needed.
Q: What happens if the customer refreshes the page? A: The session will attempt to automatically reconnect within a 2-minute window.
Q: Can I customize the agent portal UI? A: Not with the iframe approach. For custom UI, use npm integration with BYOP mode.
Cobrowse SDK - API Reference
SDK methods and events.
Initialization
const cobrowse = new ZoomCobrowse(config);Config Options
| Option | Type | Description |
|---|---|---|
sdkKey | string | Your SDK Key |
token | string | JWT token |
features.annotations | boolean | Enable annotations |
masking.selectors | array | CSS selectors to mask |
byop.enabled | boolean | Use custom PINs |
byop.pin | string | Custom PIN value |
Methods
startSession()
Start a cobrowse session.
const session = await cobrowse.startSession();
// Returns: { pin: string, sessionId: string }endSession()
End the current session.
await cobrowse.endSession();pause()
Pause screen sharing.
cobrowse.pause();resume()
Resume screen sharing.
cobrowse.resume();Events
sessionStarted
cobrowse.on('sessionStarted', (session) => {
// session.pin - PIN for agent to join
// session.sessionId - Unique session ID
});agentJoined
cobrowse.on('agentJoined', (agent) => {
// agent.name - Agent display name
// agent.userId - Agent user ID
});agentLeft
cobrowse.on('agentLeft', (agent) => {
// Agent disconnected
});sessionEnded
cobrowse.on('sessionEnded', () => {
// Session terminated
});error
cobrowse.on('error', (error) => {
// error.code - Error code
// error.message - Error description
});Resources
- SDK Reference: https://developers.zoom.us/docs/cobrowse-sdk/sdk-reference/
API Reference
This local reference points to the official Cobrowse API docs.
- API (official)
API (Reference)
Canonical source:
- API (official)
Cobrowse SDK - Authorization
JWT authentication for Cobrowse sessions.
Overview
Both customers and agents require JWTs for authentication. Generate tokens server-side.
JWT Structure
Header
{
"alg": "HS256",
"typ": "JWT"
}Payload
| Claim | Type | Description |
|---|---|---|
user_id | string | Unique user identifier |
app_key | string | Your SDK Key |
role_type | number | 1 = customer, 2 = agent |
user_name | string | Display name |
iat | number | Issued at timestamp |
exp | number | Expiration timestamp |
Strict Claim Names (Important)
Cobrowse token validation is strict. Use these claim names exactly:
user_id(notuser_identity)app_keyrole_typeuser_nameiatexp
Avoid adding unrecognized custom claims unless Zoom docs explicitly support them for your SDK version. If you see Invalid token (code 124), validate claim names first.
Role Types
| Role | Value | Description |
|---|---|---|
| Customer | 1 | User sharing their browser |
| Agent | 2 | Support staff viewing session |
Customer Token Example
const customerPayload = {
user_id: "customer_123",
app_key: "YOUR_SDK_KEY",
role_type: 1,
user_name: "John Customer",
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600
};
const token = jwt.sign(customerPayload, SDK_SECRET, { algorithm: 'HS256' });Agent Token Example
const agentPayload = {
user_id: "agent_456",
app_key: "YOUR_SDK_KEY",
role_type: 2,
user_name: "Support Agent",
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600
};
const token = jwt.sign(agentPayload, SDK_SECRET, { algorithm: 'HS256' });Security
- Generate tokens server-side only
- Never expose SDK Secret in client code
- Use reasonable expiration times
Resources
- Auth docs: https://developers.zoom.us/docs/cobrowse-sdk/authorize/
Authorization (Reference)
Canonical source:
- Authorization (official)
Zoom Cobrowse SDK Environment Variables
Standard .env keys
| Variable | Required | Used for | Where to find |
|---|---|---|---|
ZOOM_SDK_KEY | Yes | SDK identity | Zoom Marketplace -> Cobrowse/Contact Center SDK app -> App Credentials |
ZOOM_SDK_SECRET | Yes | SDK auth signing | Zoom Marketplace -> Cobrowse/Contact Center SDK app -> App Credentials |
COBROWSE_BASE_URL | Optional | Regional/tenant SDK endpoint override | Cobrowse SDK documentation or tenant provisioning details |
Runtime-only values
ZOOM_COBROWSE_SESSION_TOKEN
If your implementation mints short-lived tokens, store them in memory/cache only.
Notes
- Keep
ZOOM_SDK_SECRETserver-side. - Some samples use aliases (
SDK_KEY,SDK_SECRET); normalize internally.
Error Codes
Use the official Cobrowse docs and API behavior notes for code-level troubleshooting.
- Get Started (official)
- API (official)
Cobrowse SDK - Features
Annotations, masking, and advanced features.
Overview
Cobrowse SDK includes features for privacy, collaboration, and customization.
Annotations
Agents can draw and highlight on the shared screen.
Enable Annotations
const cobrowse = new ZoomCobrowse({
sdkKey: SDK_KEY,
token: token,
features: {
annotations: true
}
});Annotation Tools
| Tool | Description |
|---|---|
| Pointer | Highlight cursor position |
| Draw | Freehand drawing |
| Highlight | Transparent highlight |
| Arrow | Point to elements |
Privacy Masking
Hide sensitive information from agents.
Mask Elements
<!-- Add data attribute to sensitive fields -->
<input type="text" data-cobrowse-mask="true" placeholder="SSN" />
<input type="password" data-cobrowse-mask="true" />
<div data-cobrowse-mask="true">Sensitive content</div>Mask by CSS Selector
const cobrowse = new ZoomCobrowse({
sdkKey: SDK_KEY,
token: token,
masking: {
selectors: [
'.sensitive-data',
'#credit-card-field',
'[data-private]'
]
}
});Bring Your Own PIN (BYOP)
Use your own PIN system instead of Zoom-generated PINs.
const cobrowse = new ZoomCobrowse({
sdkKey: SDK_KEY,
token: token,
byop: {
enabled: true,
pin: 'YOUR_CUSTOM_PIN'
}
});Session Control
End Session
cobrowse.endSession();Pause/Resume
cobrowse.pause();
cobrowse.resume();Events
cobrowse.on('sessionStarted', (session) => {
console.log('Session started:', session.pin);
});
cobrowse.on('agentJoined', (agent) => {
console.log('Agent joined:', agent.name);
});
cobrowse.on('sessionEnded', () => {
console.log('Session ended');
});Resources
- Features docs: https://developers.zoom.us/docs/cobrowse-sdk/add-features/
Features (Reference)
Canonical source:
- Features (official)
Cobrowse SDK - Get Started
Set up collaborative browsing on your website.
Overview
This guide walks through integrating the Cobrowse SDK for customer-initiated sessions.
Prerequisites
1. SDK Universal Credit on your Zoom account 2. SDK Key and Secret 3. Token server for JWT generation
Step 1: Get SDK Credentials
1. In Zoom Workplace, go to Advanced → Zoom CPaaS → Manage 2. Click Build App 3. Locate SDK Key and SDK Secret
Step 2: Set Up Token Server
Generate JWTs server-side to protect your SDK Secret.
const jwt = require('jsonwebtoken');
function generateCobrowseToken(userId, userName, roleType) {
const iat = Math.floor(Date.now() / 1000);
const exp = iat + 3600; // 1 hour
const payload = {
user_id: userId,
app_key: SDK_KEY,
role_type: roleType, // 1 = customer, 2 = agent
user_name: userName,
iat: iat,
exp: exp
};
return jwt.sign(payload, SDK_SECRET, { algorithm: 'HS256' });
}Step 3: Integrate Customer SDK
Add to your website:
<script src="https://cobrowse.zoom.us/sdk.js"></script>
<script>
async function startCobrowse() {
// Get token from your server
const token = await fetch('/api/cobrowse-token').then(r => r.json());
const cobrowse = new ZoomCobrowse({
sdkKey: 'YOUR_SDK_KEY',
token: token.jwt
});
const session = await cobrowse.startSession();
// Display PIN to customer
alert(`Your session PIN: ${session.pin}`);
}
</script>
<button onclick="startCobrowse()">Start Support Session</button>Step 4: Set Up Agent View
Agents join via iframe:
<iframe
src="https://cobrowse.zoom.us/agent?pin={PIN}"
allow="camera; microphone"
></iframe>Next Steps
- Configure privacy masking
- Set up annotations
- Implement Bring Your Own PIN
Resources
- Cobrowse docs: https://developers.zoom.us/docs/cobrowse-sdk/get-started/
Get Started (Reference)
Canonical source:
- Get Started (official)
Session Events Reference
Event names and payload behavior are covered in official Cobrowse API documentation.
- API (official)
Settings Reference
Initialization and runtime settings are documented in the official Cobrowse references.
- Features (official)
- API (official)
Cobrowse 5-Minute Preflight Runbook
Use this before deep debugging. It catches the most common Cobrowse failures quickly.
Skill Doc Standard Note
- Agent-skill standard entrypoint is
SKILL.md. - This runbook is an operational convention (recommended), not a required skill file.
SKILL.mdis also a navigation convention for larger skill docs.
1) Confirm Two-Role Model
- Customer role (
role_type=1) starts session. - Agent role (
role_type=2) joins session.
If your demo only has one generic role, expect broken join behavior.
2) Confirm PIN Source of Truth
- Use customer SDK event
pincode_updatedas the only user-facing PIN. - Agent must join with that same PIN.
- Do not show provisional/debug PIN values from backend records.
Common symptom if wrong: Pincode is not found / error 30308.
3) Confirm JWT Claims
- Sign JWT on backend only.
- Include required claim names exactly (for example
user_id, not custom aliases). - Use SDK Key for SDK token context; keep SDK Secret server-side.
If claim names are wrong, token is rejected before session logic.
4) Confirm Session Order
Recommended sequence: 1. Customer gets customer JWT and starts session. 2. PIN is generated on customer side (pincode_updated). 3. Agent gets agent JWT and joins with that PIN.
Starting agent flow before customer session is active often causes join failures.
5) Confirm Distribution Pattern
- CDN path: customer SDK + Zoom-hosted agent desk iframe.
- npm path: custom integration (BYOP mode required for custom PIN control).
If using npm agent integration without BYOP expectations, flow mismatches happen.
6) Confirm Browser and Security Constraints
- HTTPS required (except loopback/local dev).
- CSP/CORS must allow Zoom domains.
- Third-party cookie/privacy settings can affect reconnect behavior.
Do not treat extension/adblock warnings as root cause until API/session checks fail.
7) Quick Checks (Backend + UI)
- Backend config endpoint returns expected credential flags.
- Customer page shows a single Support PIN from SDK event.
- Agent page join uses same Support PIN and returns actionable response (not generic 404).
Copy/Paste Validation Commands
curl -sS -i "$COBROWSE_BASE_URL/customer"
curl -sS -i "$COBROWSE_BASE_URL/agent"
curl -sS -i "$COBROWSE_BASE_URL/api/config"Expected: customer/agent pages load and config endpoint returns valid JSON flags.
8) Fast Decision Tree
- Invalid token -> check JWT claim names and signing secret.
- Agent cannot find PIN -> wrong PIN source or wrong session order.
- Session drops on refresh -> check reconnection window and browser privacy/cookies.
- Works locally, fails prod -> check HTTPS, CSP/CORS, reverse proxy pathing.
Browser Compatibility
Validate your supported browser matrix and test privacy/cookie constraints that may affect sessions.
See:
- Features (official)
- Get Started
Common Issues
Quick diagnostics for Zoom CoBrowse SDK issues.
- Ensure SDK script/package is loaded.
- Verify role-specific JWT generation on server.
- Validate token expiry and clock skew.
- Confirm session PIN flow between customer and agent.
Docs Links / 404s
Symptom: Official doc links you found are stale or return 404.
Fix:
- Prefer the curated references under
references/(these are meant to stay stable even if external URLs drift). - If you need working code, start from official sample repos referenced by the skill, then adapt to your stack.
Confusing "Who Creates the Session?"
Symptom: You built an "agent creates session" endpoint, but the customer flow seems to actually start the share / generate the PIN.
Fix:
- Treat customer start/share as the action that creates the shareable context (PIN/session), then the agent joins using that PIN/session info.
- Keep your server responsibilities narrow: token minting, optional auditing, and routing; avoid inventing "session creation" semantics that the SDK already owns.
Two PIN Values (Most Common Integration Mistake)
Symptom: UI shows one PIN from backend/session record and another PIN from SDK event, agent gets Pin not found or Cobrowse code not found.
Fix:
- Treat
session.on("pincode_updated")as the authoritative support PIN for agent entry. - Display exactly one primary PIN in UI (label it clearly as "Support PIN").
- Do not surface provisional/debug PINs to users.
- When opening agent page with
?pin=..., prefer freshly generated links and avoid stale bookmarks.
Agent Desk Error 30308 (Pincode is not found)
Symptom: Zoom-hosted agent desk shows:
Cobrowse code not found- error code
30308
Fix:
- Ensure customer session is active and not expired before agent joins.
- Use the latest PIN emitted by
pincode_updated. - If your app restarts or uses in-memory state, persist session/PIN mapping or avoid strict local PIN gating for desk launch.
- Have agent re-enter a fresh PIN from a newly started customer session.
Plain HTML / Express Integration Friction
Symptom: Quickstarts assume Vite/modern build pipeline; your plain HTML/Express adaptation breaks.
Fix:
- Load the SDK exactly as the official snippet expects (script order matters).
- Avoid bundler-only patterns in plain HTML (ESM imports,
import.meta, etc.) unless you add a bundler.
See:
- Get Started
- Get Started (official)
CORS and CSP
For browser integrations:
- allow required Zoom domains in CSP,
- avoid blocking SDK/script origins,
- validate iframe embedding and cross-origin constraints.
See:
- Get Started
- Get Started (official)
Error Codes Troubleshooting
Use official API guidance and startup diagnostics to map error behavior.
See:
- Error Codes Reference
- API (official)
Android SDK Lifecycle
Startup
1. Initialize once in Application.onCreate. 2. Optionally set/update context user name before channel launch.
Channel Initialization
1. Get service from ZoomCCInterface. 2. Build ZoomCCItem with:
sdkTypeentryIdorapiKeyserverType- campaign fields when needed
3. service.init(item). 4. Add listener(s).
Launch
1. Chat/ZVA:
- call
login()thenfetchUI().
2. Video:
- configure preview/auto-join options as needed.
- call
fetchUI(); login is typically internal for video flow.
3. Scheduled callback:
- init with
apiKey. fetchUI().
End and Cleanup
1. End engagement (endChat / endVideo) when needed. 2. logoff() when you need to stop callbacks. 3. releaseZoomCCService(key) in teardown paths (onDestroy).
Campaign Mode
1. Request campaigns with campaign API key. 2. Select channel from campaign metadata. 3. Reinitialize service using campaign-mode item. 4. Release or end conflicting channel services before switch.
Android Service Patterns
Chat Pattern
val service = ZoomCCInterface.getZoomCCChatService()
service.init(
ZoomCCItem(
entryId = chatEntryId,
sdkType = ZoomCCIInterfaceType.CHAT,
serverType = CCServerType.CCServerWWW
)
)
service.addListener(object : ZoomCCChatListener {
override fun unreadMsgCountChanged(count: Int) {}
override fun onClientEvent(event: ClientEvent) {}
override fun onEngagementEnd(engagementId: String) {}
override fun onEngagementStart(engagementId: String) {}
override fun onLoginStatus(status: IMStatus?) {}
override fun onError(error: Int, detail: Long, description: String) {}
})
service.login()
service.fetchUI()Video Pattern
val service = ZoomCCInterface.getZoomCCVideoService()
service.init(
ZoomCCItem(
entryId = videoEntryId,
sdkType = ZoomCCIInterfaceType.VIDEO,
serverType = CCServerType.CCServerWWW
)
)
service.setVideoPreviewOption(VideoPreviewOption.ZmCCVideoPreviewOptionDefault)
service.setAutoJoinWhenVideoCreated(false)
service.setUseBackwardFacingCameraByDefault(false)
service.addListener(object : ZoomCCVideoListener {})
service.fetchUI()Scheduled Callback Pattern
val service = ZoomCCInterface.getZoomCCScheduledCallbackService()
service.init(
ZoomCCItem(
apiKey = callbackApiKey,
sdkType = ZoomCCIInterfaceType.SCHEDULED_CALLBACK,
serverType = CCServerType.CCServerWWW
)
)
service.fetchUI()Cleanup Pattern
override fun onDestroy() {
ZoomCCInterface.releaseZoomCCService(chatEntryId)
ZoomCCInterface.releaseZoomCCService(videoEntryId)
ZoomCCInterface.releaseZoomCCService(callbackApiKey)
super.onDestroy()
}Android Reference Map
Primary reference:
- https://marketplacefront.zoom.us/sdk/contact/android/index.html
Core Types
ZoomCCInterfaceZoomCCItemZoomCCContextZoomCCServiceZoomCCChatServiceZoomCCVideoServiceZoomCCScheduledCallbackService
Listener Types
ZoomCCServiceListenerZoomCCChatListenerZoomCCVideoListener
Enums
ZoomCCIInterfaceTypeClientEventIMStatusCCServerTypeVideoPreviewOption
Common Methods
- SDK init/context:
ZoomCCInterface.init(...)ZoomCCInterface.setContext(...)- service factory:
getZoomCCChatService()getZoomCCVideoService()getZoomCCZVAService()getZoomCCScheduledCallbackService()- service lifecycle:
init(item),login(),logoff(),fetchUI()- engagement control:
endChat(),endVideo()- release:
releaseZoomCCService(key)
Deprecation Notes
- Review
deprecated.htmlin each SDK version package. - Keep runtime guards for enum/value additions and optional callbacks.
Contact Center Android 5-Minute Preflight Runbook
Use this before deep debugging.
Skill Doc Standard Note
- Skill entrypoint is
SKILL.md. - This runbook is an operational convention (recommended), not a required skill file.
- SDK/API names can drift by version; validate current names against docs/raw-docs before release.
1) Confirm Integration Surface
- Confirm channel target and integration mode for Android.
- Contact Center app path and web embed path have different lifecycle rules.
- For mobile SDKs, verify native service lifecycle and listener registration order.
2) Confirm Required Credentials
entryIdfor chat/video/ZVA entry points.apiKeyfor scheduled callback and campaign/tag use cases.- If in-client app behavior is needed, verify Zoom App credentials and required scopes.
3) Confirm Lifecycle Order
1. Initialize SDK context early. 2. Get channel service and register listeners/delegates before actions. 3. Authenticate/login where required. 4. Start/fetch channel UI and handle engagement status transitions.
4) Confirm Event/State Handling
- Track state by
engagementId; do not assume single engagement forever. - Handle context-switch events without losing draft/chat workflow state.
- Keep service/channel state isolated per active engagement.
5) Confirm Cleanup + Upgrade Posture
- End channel session and release service resources cleanly.
- Forward app lifecycle callbacks for iOS integrations.
- Re-check release notes for renamed/deprecated methods before upgrades.
6) Quick Probes
- Engagement context/status APIs return valid values.
- Start/end flow works once end-to-end for target channel.
- Listener callbacks fire on switch/end events without stale state.
7) Fast Decision Tree
- UI does not open -> invalid
entryId/apiKeyor missing init/listener sequence. - Events missing -> listener registered too late or detached unexpectedly.
- Rejoin/resume fails -> lifecycle callbacks or deep-link/scheme config mismatch.
8) Source Checkpoints
Official docs
- https://developers.zoom.us/docs/contact-center/android/
- https://marketplacefront.zoom.us/sdk/contact/android/index.html
Raw docs in repo
raw-docs/developers.zoom.us/docs/contact-center/android/raw-docs/marketplacefront.zoom.us/sdk/contact/android/
Android Common Issues
SDK Works Inconsistently Across Screens
Cause:
- SDK initialized too late.
Fix:
- Initialize in
Application.onCreate.
NoClassDefFoundError / viewBinding Errors
Cause:
- Missing expected dependencies or view binding configuration.
Fix:
- Match SDK package module requirements.
- Ensure build config aligns with current SDK release notes.
Video/Chat UI Does Not Open
Cause:
- Wrong identifier type in
ZoomCCItem.
Fix:
entryIdfor chat/video/ZVA.apiKeyfor scheduled callback/campaign.
Events Not Firing
Cause:
- Listener attached after service launch or removed early.
Fix:
- Add listeners before
fetchUI.
Rejoin Link Opens Browser But Not App
Cause:
- Deep-link host/scheme mismatch.
Fix:
- Align Android manifest intent filters with generated rejoin URL format.
Contact Center Architecture and Lifecycle
This document defines a stable architecture pattern that works across Contact Center app, web, and mobile integrations.
Architecture Layers
1. Integration Surface
- Zoom Contact Center App (Zoom client embedded webview).
- Web SDK/Campaign SDK on external sites.
- Android/iOS native SDK.
2. Engagement State Layer
- Current
engagementId. - Engagement status (
start,hold,resume,end). - Engagement-scoped draft data.
3. Channel Service Layer
- Chat.
- Video.
- ZVA.
- Scheduled Callback.
4. Persistence Layer
- Transient per-engagement state cache (frontend local storage or backend session store).
- Optional backend persistence for long-running workflows and compliance logging.
Canonical Lifecycle
1. Initialize context. 2. Determine active engagement context. 3. Build/init channel service/client. 4. Register callbacks before launching UI. 5. Start channel view. 6. Process status/context events. 7. End and cleanup.
Context-Switching Contract
- Treat
engagementIdas the primary state key. - Never assume a single engagement in memory for messaging channels.
- Restore state on each engagement context change.
- Clear or archive engagement state only when end-state logic is complete.
Event-Driven Contract
- Do not poll as a primary strategy.
- Subscribe early and keep handlers idempotent.
- Handle out-of-order or repeated events safely.
Campaign Mode Pattern
1. Fetch campaigns with campaign API key. 2. Pick channel from translatedCampaignChannels. 3. Create channel item with useCampaignMode=true. 4. Launch service UI. 5. Release conflicting channel services when switching channels.
Security and Identity
- Use explicit user/session identity refresh paths (
authorize,getAppContext) for Contact Center app scenarios. - For PWA flows, do not depend on
x-zoom-app-contextheader. - Keep OAuth and app context decryption on backend where possible.
iOS SDK Lifecycle
Context Initialization
1. Create ZoomCCContext. 2. Configure user name, cache folder, and optional share settings. 3. Set context on ZoomCCInterface.sharedInstance().
Service Initialization Pattern
1. Build ZoomCCItem. 2. Select channel type:
.chat.video.ZVA.scheduledCallback
3. Populate entryId or apiKey depending on channel. 4. Get service instance. 5. Set delegate. 6. Call initialize(with:). 7. Call login() where required. 8. fetchUI and push returned view controller.
Lifecycle Bridging
Forward these app delegate callbacks:
applicationDidBecomeActive->appDidBecomeActiveapplicationWillResignActive->appWillResignActiveapplicationDidEnterBackground->appDidEnterBackgroudapplicationWillTerminate->appWillTerminate
Rejoin Flow
1. Configure app URL scheme and admin rejoin URL. 2. Forward open url callback to rejoin handler. 3. Call video service rejoin API with prepared ZoomCCItem. 4. Push returned view controller in completion block.
Cleanup
- End service-specific engagement methods:
endChatendVideoendScheduledCallback- Use service
logout/ uninitialize patterns when needed by flow design.
iOS Service Patterns
Context Setup
let context = ZoomCCContext()
context.cacheFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
context.userName = userName
context.domainType = .US01
ZoomCCInterface.sharedInstance().context = contextChat Pattern
let item = ZoomCCItem()
item.sdkType = .chat
item.entryId = chatEntryId
let chat = ZoomCCInterface.sharedInstance().chatService()
chat.chatDelegate = self
if chat.status == .initial {
chat.initialize(with: item)
chat.login()
}
chat.fetchUI { vc in
if let vc { self.navigationController?.pushViewController(vc, animated: true) }
}Video Pattern
let item = ZoomCCItem()
item.sdkType = .video
item.entryId = videoEntryId
let video = ZoomCCInterface.sharedInstance().videoService()
video.videoDelegate = self
if video.status == .initial {
video.initialize(with: item)
}
video.fetchUI { vc in
if let vc { self.navigationController?.pushViewController(vc, animated: true) }
}Scheduled Callback Pattern
let item = ZoomCCItem()
item.sdkType = .scheduledCallback
item.apiKey = callbackApiKey
let scheduled = ZoomCCInterface.sharedInstance().scheduledCallbackService()
scheduled.scheduledCallbackDelegate = self
if scheduled.status == .initial {
scheduled.initialize(with: item)
}
scheduled.fetchUI { vc in
if let vc { self.navigationController?.pushViewController(vc, animated: true) }
}Rejoin Pattern
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return rootVC.handleRejoinVideoOpenURL(url)
}iOS Reference Map
Primary references:
- https://marketplacefront.zoom.us/sdk/contact/ios/index.html
- SDK headers packaged in iOS SDK zip (
ZoomCCInterface.h)
Core Types
ZoomCCInterfaceZoomCCContextZoomCCItemZoomCCCampaignInfo
Service Protocols
ZoomCCServiceZoomCCChatServiceZoomCCVideoServiceZoomCCScheduledCallbackService
Delegate Protocols
ZoomCCServiceDelegateZoomCCChatServiceDelegateZoomCCAppLifecyleDelegate
Key Methods
- Interface:
sharedInstancechatServicezvaServicevideoServicescheduledCallbackServicegetCampaigns- Service lifecycle:
initializeWithItemloginlogoutfetchUI- Video:
handleRejoinVideoOpenURL:item:videoDelegate:complete:
Deprecation Note
onService:error:detail:is deprecated.- Use
onService:error:detail:description:.
Contact Center iOS 5-Minute Preflight Runbook
Use this before deep debugging.
Skill Doc Standard Note
- Skill entrypoint is
SKILL.md. - This runbook is an operational convention (recommended), not a required skill file.
- SDK/API names can drift by version; validate current names against docs/raw-docs before release.
1) Confirm Integration Surface
- Confirm channel target and integration mode for iOS.
- Contact Center app path and web embed path have different lifecycle rules.
- For mobile SDKs, verify native service lifecycle and listener registration order.
2) Confirm Required Credentials
entryIdfor chat/video/ZVA entry points.apiKeyfor scheduled callback and campaign/tag use cases.- If in-client app behavior is needed, verify Zoom App credentials and required scopes.
3) Confirm Lifecycle Order
1. Initialize SDK context early. 2. Get channel service and register listeners/delegates before actions. 3. Authenticate/login where required. 4. Start/fetch channel UI and handle engagement status transitions.
4) Confirm Event/State Handling
- Track state by
engagementId; do not assume single engagement forever. - Handle context-switch events without losing draft/chat workflow state.
- Keep service/channel state isolated per active engagement.
5) Confirm Cleanup + Upgrade Posture
- End channel session and release service resources cleanly.
- Forward app lifecycle callbacks for iOS integrations.
- Re-check release notes for renamed/deprecated methods before upgrades.
6) Quick Probes
- Engagement context/status APIs return valid values.
- Start/end flow works once end-to-end for target channel.
- Listener callbacks fire on switch/end events without stale state.
7) Fast Decision Tree
- UI does not open -> invalid
entryId/apiKeyor missing init/listener sequence. - Events missing -> listener registered too late or detached unexpectedly.
- Rejoin/resume fails -> lifecycle callbacks or deep-link/scheme config mismatch.
8) Source Checkpoints
Official docs
- https://developers.zoom.us/docs/contact-center/ios/
- https://marketplacefront.zoom.us/sdk/contact/ios/index.html
Raw docs in repo
raw-docs/developers.zoom.us/docs/contact-center/ios/raw-docs/marketplacefront.zoom.us/sdk/contact/ios/
iOS Common Issues
Service Starts But View Never Appears
Cause:
- Missing
fetchUIhandling or wrong navigation presentation path.
Fix:
- Ensure returned view controller is pushed/presented on main thread.
App Background/Foreground Breaks Session
Cause:
- App lifecycle callbacks not forwarded to SDK.
Fix:
- Wire app delegate lifecycle methods to
ZoomCCInterface.
Rejoin URL Arrives But Rejoin Fails
Cause:
- URL scheme mismatch or context not initialized.
Fix:
- Verify URL types config, rejoin URL settings, and context setup before calling rejoin API.
Duplicate or Stale Channel Sessions
Cause:
- Previous service instance left active during channel switches.
Fix:
- End current engagement and rebuild service item when changing channel/campaign context.
Error Callback Signature Drift
Cause:
- Implemented only deprecated callback signature.
Fix:
- Implement
onService:error:detail:description:and keep compatibility wrappers as needed.
Zoom Contact Center Environment Variables
Standard .env keys
| Variable | Required | Used for | Where to find |
|---|---|---|---|
ZOOM_CLIENT_ID | Yes (API/OAuth integrations) | OAuth app identity for Contact Center APIs | Zoom Marketplace -> OAuth app -> App Credentials |
ZOOM_CLIENT_SECRET | Yes (API/OAuth integrations) | OAuth token exchange | Zoom Marketplace -> OAuth app -> App Credentials |
ZOOM_REDIRECT_URI | User OAuth flow | OAuth callback URL | Zoom Marketplace -> OAuth redirect/allow list |
ZCC_CHAT_ENTRY_ID | Web/chat entry flows | Contact Center chat entry point routing | Contact Center Admin -> Flows -> Entry Points |
ZCC_VIDEO_ENTRY_ID | Video engagement flows | Contact Center video entry point routing | Contact Center Admin -> Flows -> Entry Points |
ZCC_ZVA_ENTRY_ID | Optional (Virtual Agent) | Virtual agent entry routing | Contact Center Admin -> Flows -> Entry Points |
ZCC_CAMPAIGN_API_KEY | Campaign/web embed mode | Campaign authorization for web embed | Contact Center Admin -> Campaign Management -> Web and In-App -> Embed Web Tag |
ZCC_WEB_API_KEY | Web SDK/embed mode | Client-side Contact Center embed initialization | Contact Center Admin -> Campaign Management -> Web and In-App -> Embed Web Tag |
ZCC_SCHEDULED_CALLBACK_API_KEY | Scheduled callback flows | Callback scheduling authorization | Contact Center campaign/flow callback configuration |
Runtime-only values
ZOOM_ACCESS_TOKEN- Contact/session IDs issued by Contact Center runtime APIs
Notes
- Contact Center implementations often mix OAuth credentials with flow/campaign keys.
- Keep OAuth secrets and campaign keys out of client-side source control.
Forum-Derived Top Questions (Contact Center)
Use this as a checklist of the most common recent Developer Forum asks for Zoom Contact Center integrations.
Fast Routing Questions (Ask First)
- Surface: Contact Center app in Zoom client, web SDK/campaign embed, Smart Embed, or REST API workflow.
- Runtime: web vs Android vs iOS and exact SDK version.
- Auth context: app type, scopes, token owner, and Contact Center admin role.
- Resource target: queue/flow/engagement IDs and expected channel (
voice,video,chat, callback). - Failure proof: exact endpoint/event, full response code/message, and one representative payload.
Smart Embed Login/Origin Problems
Common asks:
- Login popup completes but embed never receives session.
- Hosted environment fails while local HTML test works.
Answer pattern:
- Verify allowed domain configuration exactly matches production origin.
- Validate
originusage andpostMessagecontract assumptions. - Check iframe/sandbox/CSP restrictions for hosted environments.
- Reproduce with a minimal page (embed only) to isolate app-layer interference.
Token Works for Phone But Contact Center API Returns 401
Common asks:
- Same bearer token can call Phone endpoints but Contact Center endpoints return invalid token.
Answer pattern:
- Confirm Contact Center scopes are on the active token (not only app config).
- Confirm requester has Contact Center admin permissions in target account.
- Confirm account context did not drift (owner/admin reassignment can break behavior).
- Regenerate token after any scope/role changes.
Event Gaps and State-Change Confusion
Common asks:
contact_center.user_status_changedor engagement events appear missing.- Documented event name does not fire as expected in a given lifecycle.
Answer pattern:
- Attach listeners before channel/session start.
- Verify event coverage for the specific channel and engagement phase.
- Confirm network/security layers are not blocking webhook deliveries.
- Add reconciliation logic instead of assuming every state transition emits one event.
Recordings and Transcripts Edge Cases
Common asks:
- Recording rows exist but media/transcript is unavailable.
- Transcript download fails or payload differs from expectations.
Answer pattern:
- Check recording duration/status before download attempts.
- Handle not-ready and no-recording states explicitly.
- Retry with bounded backoff for newly completed engagements.
- Keep fallback handling for empty/partial recording metadata.
Analytics Pagination Repeats First Page
Common asks:
next_page_tokenloops the same records in historical analytics endpoints.
Answer pattern:
- Keep all filter params stable while paging.
- Use token exactly as returned; do not mutate sort/filter inputs mid-stream.
- Add duplicate-page detection and stop conditions in client code.
Data Availability Boundaries
Common asks:
- Access to in-progress chat messages or other live interaction internals.
Answer pattern:
- Distinguish near-real-time events from post-engagement reporting APIs.
- Set expectations early when an in-progress data surface is unavailable.
- Design workflows around available lifecycle events and finalized engagement data.
Samples Validation Summary
This summary captures lifecycle and architecture checks against these references:
- Web:
- https://github.com/zoom/ZCC-Zoom-App-Advanced-Sample
- https://github.com/zoom/zcc-javascript-quickstart
- https://github.com/zoom/zcc-nextjs-sample
- iOS package:
ios-zccsdk-5.2.0.zip - Android package:
android-zccsdk-5.2.0.zip
Confirmed Lifecycle Patterns
1. Contact Center App (Zoom Apps SDK):
- Configure capabilities.
- Query engagement context/status.
- Subscribe to engagement change events.
- Persist state by
engagementId.
2. Android Native:
- Initialize in
Application.onCreate. - Service
initwithZoomCCItem. - Use
fetchUIto present channel. logoffandreleaseZoomCCServiceon cleanup.
3. iOS Native:
- Set
ZoomCCInterface.sharedInstance().context. - Initialize service with item.
- Use
fetchUIto present. - Forward app lifecycle callbacks to SDK.
- Use rejoin handler path for video reconnect.
Contradictions and Drift Signals
- Some docs show simplified
service.init("EntryId")signatures while current references emphasize item-based initialization. - iOS deprecated error callback still appears in older sample/docs.
- Some public sample manifests contain values that conflict with expected Contact Center embedding configuration and should be reviewed per environment.
- Scraped reference pages include parser artifacts (
TODO/error pages) and should not be treated as canonical API surfaces.
Operational Guidance
- Treat samples as architecture guidance, not immutable source of truth.
- Resolve conflicts in this order:
1. Current official docs. 2. Current platform API reference. 3. Latest shipped SDK headers/binaries. 4. Samples.
Versioning and Compatibility Notes
Minimum Version Enforcement
- Zoom enforces SDK minimum versions quarterly.
- Enforcement windows are announced with advance notice.
- Older SDKs can stop functioning in production even if code has not changed.
Practical Policy
1. Track SDK version in runtime telemetry. 2. Maintain a scheduled upgrade cadence. 3. Validate critical flows every release:
- launch/init
- engagement events
- channel open/close
- rejoin (mobile)
Known Drift Patterns
- API shape drift between docs and generated references.
- Legacy snippets showing old method signatures.
- Event naming/style differences between product surfaces.
- Deprecated callbacks preserved for backward compatibility but replaced in newer signatures.
iOS Notable Deprecation
onService:error:detail:is deprecated.- Prefer
onService:error:detail:description:.
Smart Embed Version Note
- Smart Embed v3 is the forward path in docs.
- Maintain version-gated integration code if your account still has older embed behavior.
Defensive Design
- Feature-detect methods/events before calling them.
- Keep adapters between your domain model and SDK payloads.
- Avoid hard-coding assumptions about optional fields.
Contact Center 5-Minute Preflight Runbook
Use this before deep debugging. It catches the most common Zoom Contact Center integration failures quickly.
Skill Doc Standard Note
- Skill entrypoint is
SKILL.md. - This runbook is an operational convention (recommended), not a required skill file.
SKILL.mdis a navigation convention for larger skill docs.
1) Confirm Integration Path
- Contact Center app inside Zoom client: use Zoom Apps SDK APIs/events (
getEngagementContext,onEngagementStatusChange, etc.). - Website embed: use Contact Center web SDK/campaign script path.
- Native mobile app: use Android/iOS Contact Center SDK binaries and service lifecycle.
Wrong path is the top source of confusion.
2) Confirm Required Credentials
entryIdfor chat/video/ZVA channels.apiKeyfor scheduled callback and campaign/web-tag scenarios.- If building a Contact Center app in Zoom client, validate app credentials and OAuth setup in Marketplace.
3) Confirm Lifecycle Order
Common native/mobile order: 1. Initialize SDK context early. 2. Get service instance. 3. Initialize service with ZoomCCItem. 4. Register listener/delegate. 5. login() where required (typically chat/ZVA). 6. fetchUI() to present the channel view.
Web app path: 1. zoomSdk.config(...) 2. getEngagementContext() and getEngagementStatus() 3. subscribe to onEngagementContextChange and onEngagementStatusChange 4. persist state keyed by engagementId
4) Confirm Context Switching Behavior
- A single app instance can receive multiple engagement contexts.
- Persist draft/workflow state by
engagementId. - Do not assume only one active engagement for chat/SMS/email workflows.
5) Confirm Cleanup Semantics
- End action (
endChat,endVideo,endScheduledCallback) is not the same as service release. - Apply platform-specific cleanup (
logout/logoff, release/uninitialize APIs). - On iOS, forward app lifecycle callbacks (
appDidBecomeActive,appWillTerminate, etc.) toZoomCCInterface.
6) Version + Drift Checks
- Zoom enforces minimum SDK versions quarterly (first weekend of February, May, August, November).
- Re-check docs and changelog before release; naming and signatures can drift.
- Watch deprecations:
- iOS
onService:error:detail:is deprecated in favor ofonService:error:detail:description:.
7) Quick Probes
- App context/status APIs return valid values.
- Engagement events fire when agent switches engagements.
- Chat/video/scheduled callback can be started and ended once each without stale state.
- No CSP or domain allow-list blocks for web integrations.
8) Fast Decision Tree
- No engagement data in Contact Center app -> missing SDK
configcapabilities or wrong runtime context. - Channel UI does not open -> invalid
entryId/apiKey, missing init, or wrong service/channel mapping. - Events not firing on switch/end -> listeners not attached early enough or removed incorrectly.
- Rejoin fails on mobile -> deep-link/scheme configuration mismatch.
High-Level Scenarios
1. Agent Notes App in Contact Center
Goal:
- Agent writes notes that follow engagement context switching.
Flow: 1. config Zoom Apps SDK with engagement capabilities. 2. Load getEngagementContext + getEngagementStatus. 3. Store notes by engagementId. 4. On onEngagementContextChange, swap UI state to selected engagement. 5. On onEngagementStatusChange end, finalize or clear engagement draft.
2. Web Chat Campaign Launch
Goal:
- Product team controls targeting in admin without code redeploy.
Flow: 1. Add campaign web tag script. 2. Wait for zoomCampaignSdk:ready. 3. Programmatically open/show/hide/close as needed. 4. Listen for engagement events for analytics and CRM writes.
3. Mobile Chat and Video with Native SDK
Goal:
- Customer mobile app can launch chat/video and recover from interruptions.
Flow: 1. Initialize SDK context in app startup. 2. Build ZoomCCItem for channel. 3. Initialize service, attach delegates/listeners, and launch UI. 4. Handle disconnect/rejoin links for video. 5. End flow and release service resources.
4. Campaign-Mode Channel Router
Goal:
- Runtime selection of chat/video/ZVA/scheduled callback per campaign.
Flow: 1. Fetch campaigns by API key. 2. Inspect campaign channels. 3. Build channel-specific item with campaign mode. 4. Release previous conflicting service before opening new channel.
5. Smart Embed CRM Integration
Goal:
- Embed Contact Center softphone in CRM with screen-pop and contact lookup.
Flow: 1. Load Smart Embed iframe. 2. Handle postMessage events (zcc-init-config-request, search, resize, engagement events). 3. Return contact search results and route screen-pop in CRM. 4. Keep feature flags aligned with Smart Embed version path.
Common Drift and Breaks
Symptom: Engagement Context Missing
Likely causes:
- App is not running in Contact Center context.
- Missing SDK capabilities in
config. - Identity/context token path is incomplete.
Checks: 1. Confirm running context. 2. Confirm capabilities include engagement APIs/events. 3. Confirm app manifest and feature toggles.
Symptom: Campaign SDK Methods Throw
Likely causes:
- Calling methods before
zoomCampaignSdk:ready. - Invalid API key or missing campaign configuration.
- Script blocked by CSP/ad-blockers/tag-manager path.
Checks: 1. Add ready gate before method calls. 2. Validate key/env and script URL. 3. Validate CSP/domain allow lists.
Symptom: Native Service Not Responding
Likely causes:
- SDK init executed too late.
- Wrong channel item (
entryIdvsapiKeymismatch). - Listeners/delegates attached after service start.
Checks: 1. Move init earlier in app lifecycle. 2. Validate item/channel pairing. 3. Register listeners before fetchUI.
Symptom: Rejoin Flow Fails
Likely causes:
- Deep link scheme/host mismatch.
- Rejoin URL or web relay page not configured.
- App lifecycle hooks/context not initialized.
Checks: 1. Verify platform URL/deep link configuration. 2. Verify admin rejoin settings. 3. Verify rejoin handler wiring.
Symptom: Behavior Changed After Release
Likely causes:
- Minimum version enforcement date reached.
- Deprecated callback removed or changed.
- New SDK defaults in channel behavior.
Checks: 1. Confirm SDK version in production. 2. Review changelog/deprecation notes. 3. Add adapter guards for optional fields/methods.
Web Lifecycle and Event Model
Contact Center App Runtime (Zoom Client)
1. Configure SDK capabilities. 2. Read running context. 3. Read engagement context/status. 4. Subscribe to:
onEngagementContextChangeonEngagementStatusChange- optional variable change events
5. Maintain engagement-scoped state.
Web Campaign SDK Runtime
1. Load script with API key. 2. Wait for zoomCampaignSdk:ready. 3. Call methods:
opencloseshowhideendChat
4. Subscribe/unsubscribe to SDK events.
Video Client Runtime
1. Create client. 2. Initialize with entry identifier and optional metadata. 3. Start video. 4. Handle video-start and video-end events.
Smart Embed Runtime
1. Load Smart Embed iframe. 2. Listen for message events from iframe. 3. Respond to init/search/control requests. 4. Map engagement and contact data to CRM/app entities.
State Strategy
- Key all session data by
engagementId. - Keep event handlers re-entrant and idempotent.
- Treat
endstatus as cleanup boundary.
Web Example: Engagement-Aware State
await zoomSdk.config({
version: "0.16.0",
capabilities: [
"getRunningContext",
"getEngagementContext",
"getEngagementStatus",
"onEngagementContextChange",
"onEngagementStatusChange",
],
});
const stateByEngagement = new Map();
let currentEngagementId = "";
function ensureState(id) {
if (!stateByEngagement.has(id)) {
stateByEngagement.set(id, { notes: "", formDraft: {} });
}
return stateByEngagement.get(id);
}
async function hydrate() {
const [ctx, status] = await Promise.all([
zoomSdk.callZoomApi("getEngagementContext"),
zoomSdk.callZoomApi("getEngagementStatus"),
]);
currentEngagementId = ctx?.engagementContext?.engagementId || "";
if (currentEngagementId) ensureState(currentEngagementId);
render(currentEngagementId, status?.engagementStatus?.state);
}
zoomSdk.addEventListener("onEngagementContextChange", (evt) => {
currentEngagementId = evt?.engagementContext?.engagementId || "";
if (currentEngagementId) ensureState(currentEngagementId);
render(currentEngagementId);
});
zoomSdk.addEventListener("onEngagementStatusChange", (evt) => {
const state = evt?.engagementStatus?.state;
if (state === "end" && currentEngagementId) {
stateByEngagement.delete(currentEngagementId);
}
render(currentEngagementId, state);
});
hydrate();Campaign SDK Ready Gate
window.addEventListener("zoomCampaignSdk:ready", () => {
if (!window.zoomCampaignSdk) return;
window.zoomCampaignSdk.show();
});Web Reference Map
Primary docs:
- https://developers.zoom.us/docs/contact-center/web/get-started/
- https://developers.zoom.us/docs/contact-center/web/chat/
- https://developers.zoom.us/docs/contact-center/web/video/
- https://developers.zoom.us/docs/contact-center/web/campaigns/
- https://developers.zoom.us/docs/contact-center/web/sdk-reference/
- https://developers.zoom.us/docs/contact-center/smart-embed/
Engagement APIs/Events (Contact Center App)
getEngagementContextgetEngagementStatusonEngagementContextChangeonEngagementStatusChangeonEngagementVariableValueChange
Campaign SDK Events
opencloseshowhideengagement_startedengagement_ended
Campaign SDK Methods
open()close()show()hide()endChat()waitForInit()waitForReady()updateUserContext()
Video Client Events
video-startvideo-endnotification-join-callvideo-click-endvideo-force-endtask-created
Smart Embed Event Surface
- init/config events (
zcc-init-config-request,zcc-init-config-response) - engagement and channel events
- contact search request/response patterns
- resize and interaction events
Contact Center Web 5-Minute Preflight Runbook
Use this before deep debugging.
Skill Doc Standard Note
- Skill entrypoint is
SKILL.md. - This runbook is an operational convention (recommended), not a required skill file.
- SDK/API names can drift by version; validate current names against docs/raw-docs before release.
1) Confirm Integration Surface
- Confirm channel target and integration mode for Web.
- Contact Center app path and web embed path have different lifecycle rules.
- For mobile SDKs, verify native service lifecycle and listener registration order.
2) Confirm Required Credentials
entryIdfor chat/video/ZVA entry points.apiKeyfor scheduled callback and campaign/tag use cases.- If in-client app behavior is needed, verify Zoom App credentials and required scopes.
3) Confirm Lifecycle Order
1. Initialize SDK context early. 2. Get channel service and register listeners/delegates before actions. 3. Authenticate/login where required. 4. Start/fetch channel UI and handle engagement status transitions.
4) Confirm Event/State Handling
- Track state by
engagementId; do not assume single engagement forever. - Handle context-switch events without losing draft/chat workflow state.
- Keep service/channel state isolated per active engagement.
5) Confirm Cleanup + Upgrade Posture
- End channel session and release service resources cleanly.
- Forward app lifecycle callbacks for iOS integrations.
- Re-check release notes for renamed/deprecated methods before upgrades.
6) Quick Probes
- Engagement context/status APIs return valid values.
- Start/end flow works once end-to-end for target channel.
- Listener callbacks fire on switch/end events without stale state.
7) Fast Decision Tree
- UI does not open -> invalid
entryId/apiKeyor missing init/listener sequence. - Events missing -> listener registered too late or detached unexpectedly.
- Rejoin/resume fails -> lifecycle callbacks or deep-link/scheme config mismatch.
8) Source Checkpoints
Official docs
- https://developers.zoom.us/docs/contact-center/web/
- https://developers.zoom.us/docs/contact-center/web/sdk-reference/
Raw docs in repo
raw-docs/developers.zoom.us/docs/contact-center/web/
Web Common Issues
zoomCampaignSdk Is Undefined
Cause:
- Calls happen before readiness event.
Fix:
- Wait for
zoomCampaignSdk:readybefore calling SDK methods.
Widget Does Not Load
Cause:
- CSP or domain allow-list blocks script/network access.
Fix:
- Update CSP headers and Marketplace domain allow list entries.
App Context Header Missing in PWA
Cause:
- PWA path does not provide
x-zoom-app-contextheader consistently.
Fix:
- Use
getAppContext()and backend token decryption flow.
Engagement Data Gets Overwritten
Cause:
- State keyed globally instead of by
engagementId.
Fix:
- Persist and restore state per engagement key.
Smart Embed Events Not Received
Cause:
- postMessage listener origin/type filtering missing or incorrect.
Fix:
- Implement strict message handling and respond to required init/search events.
App Types
Choose the right Zoom app type for your integration.
Overview
Zoom Marketplace has 3 app types:
| App Type | Use Case |
|---|---|
| General App | Flexible - configure surfaces, embeds, OAuth, webhooks |
| Server-to-Server OAuth | Backend automation, no user authorization |
| Webhook Only | Receive events only, no API access |
General App
The modular app type. Pick what you need:
OAuth Type (choose one)
| Type | Scopes | Authorization |
|---|---|---|
| Admin | Admin scopes (*:admin) | Entire account OR specific users |
| User | User scopes | Only themselves (self-service) |
Surfaces (product contexts)
Your app can interact with these Zoom products:
- Meetings
- Webinars
- Rooms
- Phone
- Team Chat
- Contact Center
- Whiteboard
- Virtual Agent
- Events
- Workflows
Embeds (SDKs)
Embed Zoom functionality in your app:
| Embed | Description |
|---|---|
| Meeting SDK | Embed Zoom meetings |
| Contact Center SDK | Embed Contact Center |
| Phone SDK | Embed Phone functionality |
Access
Configure in the Access tab:
- Secret Token - Verify webhook notifications
- Event Subscription - Webhooks
- WebSockets - Real-time event connections
Scopes
Define which API methods the app can call. Scopes are:
- Restricted to specific resources
- Reviewed by Zoom during app submission
Features in General App
General App can also include:
- Zoom Apps - Apps that run inside Zoom client
Server-to-Server OAuth
Backend automation without user authorization.
- No user interaction required
- Access your account's data
- Can include webhooks and zoom-websockets
- Best for: automation, reporting, integrations
Webhook Only
Event notifications only.
- Receive events, no API calls
- No OAuth tokens needed
- Best for: event logging, triggering external workflows
Use this when you ONLY need events. Otherwise, add webhooks to General App or S2S.
Decision Guide
| Need | App Type |
|---|---|
| Call APIs for your account (backend) | Server-to-Server OAuth |
| Call APIs on behalf of users | General App (Admin or User OAuth) |
| Embed Zoom meetings | General App + Meeting SDK embed |
| Embed Contact Center | General App + Contact Center SDK embed |
| Embed Phone | General App + Phone SDK embed |
| Build in-client app | General App + Zoom Apps |
| Receive events only | Webhook Only |
| Receive events + call APIs | General App or S2S (with webhooks) |
Resources
- App types docs: https://developers.zoom.us/docs/integrations/
- Marketplace: https://marketplace.zoom.us/
Authentication
Authentication methods for Zoom APIs and SDKs.
Overview
Zoom supports multiple authentication methods depending on your use case:
| Method | Use Case |
|---|---|
| OAuth 2.0 | User-authorized access (on behalf of user) |
| Server-to-Server OAuth | Server-side automation (no user interaction) |
| SDK JWT | Meeting SDK and Video SDK authentication |
OAuth 2.0
For apps that act on behalf of users.
Flow
1. User clicks "Connect with Zoom"
2. Redirect to Zoom authorization URL
3. User grants permission
4. Zoom redirects back with auth code
5. Exchange code for access token
6. Use token to call APIsAuthorization URL
https://zoom.us/oauth/authorize?response_type=code&client_id={clientId}&redirect_uri={redirectUri}Token Exchange
curl -X POST "https://zoom.us/oauth/token" \
-H "Authorization: Basic {base64(clientId:clientSecret)}" \
-d "grant_type=authorization_code&code={authCode}&redirect_uri={redirectUri}"Server-to-Server OAuth
For server-side automation without user interaction.
Get Access Token
curl -X POST "https://zoom.us/oauth/token?grant_type=account_credentials&account_id={accountId}" \
-H "Authorization: Basic {base64(clientId:clientSecret)}"Response
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600
}SDK JWT Signatures
For Meeting SDK and Video SDK authentication. See:
- Meeting SDK Authorization
- Video SDK Authorization
Best Practices
| Practice | Recommendation |
|---|---|
| Expiry (`exp`) | Set ~10 seconds after generation |
| Issued At (`iat`) | Set 2 hours in the past (if exp - iat >= 2 hours required) |
| Generate server-side | Never expose secrets in client code |
Resources
- OAuth docs: https://developers.zoom.us/docs/integrations/oauth/
- S2S OAuth docs: https://developers.zoom.us/docs/internal-apps/s2s-oauth/
Automatic Skill Chaining: REST API + Webhooks
This guide provides executable patterns for handling a multi-faceted workflow that needs both:
- synchronous REST API operations (
zoom-rest-api) - asynchronous event processing (
zoom-webhooks)
Chain Selection Logic
export type SkillChain = {
selectedSkills: string[];
executionOrder: string[];
};
export function chooseRestWebhookChain(query: string): SkillChain {
const q = query.toLowerCase();
const needsRest = /create meeting|update meeting|list users|rest api|\/v2\//.test(q);
const needsWebhook = /webhook|event|meeting\.started|participant|real-time update/.test(q);
const selectedSkills = ['zoom-general'];
if (needsRest || needsWebhook) selectedSkills.push('zoom-oauth');
if (needsRest) selectedSkills.push('zoom-rest-api');
if (needsWebhook) selectedSkills.push('zoom-webhooks');
return {
selectedSkills,
executionOrder: selectedSkills,
};
}Reference Architecture
Client/API Caller
-> Orchestrator API
-> OAuth token manager
-> REST API worker (create/update meetings)
-> Persistence (meeting state + idempotency keys)
<- immediate REST result
Zoom Event Pipeline
Zoom -> Webhook ingress (signature verify + URL validation)
-> Queue
-> Event processors
-> State projection / downstream notificationsMinimal Runnable Example (Node.js)
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.json({
verify: (req, _res, buf) => {
req.rawBody = buf.toString('utf8');
},
}));
const tokenCache = { accessToken: '', expiresAt: 0 };
const meetingStore = new Map();
async function getAccessToken() {
const now = Date.now();
if (tokenCache.accessToken && now < tokenCache.expiresAt - 60_000) {
return tokenCache.accessToken;
}
const params = new URLSearchParams({
grant_type: 'account_credentials',
account_id: process.env.ZOOM_ACCOUNT_ID,
});
const basic = Buffer.from(`${process.env.ZOOM_CLIENT_ID}:${process.env.ZOOM_CLIENT_SECRET}`).toString('base64');
const res = await fetch(`https://zoom.us/oauth/token?${params}`, {
method: 'POST',
headers: { Authorization: `Basic ${basic}` },
});
if (!res.ok) throw new Error(`token_exchange_failed:${res.status}`);
const data = await res.json();
tokenCache.accessToken = data.access_token;
tokenCache.expiresAt = now + data.expires_in * 1000;
return tokenCache.accessToken;
}
app.post('/api/meetings', async (req, res) => {
try {
const token = await getAccessToken();
const hostUserId = process.env.ZOOM_HOST_USER_ID;
if (!hostUserId) {
return res.status(500).json({ error: 'missing_host_user_id', detail: 'Set ZOOM_HOST_USER_ID for S2S meeting creation' });
}
const body = {
topic: req.body.topic || 'Auto Meeting',
type: 2,
start_time: req.body.start_time,
duration: req.body.duration || 30,
timezone: req.body.timezone || 'UTC',
};
const z = await fetch(`https://api.zoom.us/v2/users/${encodeURIComponent(hostUserId)}/meetings`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await z.json();
if (!z.ok) return res.status(z.status).json(data);
meetingStore.set(String(data.id), { status: 'scheduled', topic: data.topic, participants: 0 });
return res.status(201).json(data);
} catch (err) {
return res.status(500).json({ error: 'create_meeting_failed', detail: String(err) });
}
});
function verifySignature(req) {
const ts = req.headers['x-zm-request-timestamp'];
const sig = req.headers['x-zm-signature'];
const msg = `v0:${ts}:${req.rawBody || ''}`;
const expected = `v0=${crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET).update(msg).digest('hex')}`;
return sig === expected;
}
app.post('/webhooks/zoom', (req, res) => {
if (req.body.event === 'endpoint.url_validation') {
const plainToken = req.body.payload?.plainToken;
const encryptedToken = crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET).update(plainToken).digest('hex');
return res.json({ plainToken, encryptedToken });
}
if (!verifySignature(req)) return res.status(401).send('invalid_signature');
const evt = req.body.event;
const id = String(req.body.payload?.object?.id || '');
if (id && !meetingStore.has(id)) meetingStore.set(id, { status: 'unknown', participants: 0 });
const state = meetingStore.get(id);
if (state) {
if (evt === 'meeting.started') state.status = 'in_progress';
if (evt === 'meeting.ended') state.status = 'ended';
if (evt === 'meeting.participant_joined') state.participants += 1;
if (evt === 'meeting.participant_left') state.participants = Math.max(0, state.participants - 1);
}
return res.status(200).send('ok');
});
app.listen(process.env.PORT || 3001, () => {
console.log('orchestrator listening');
});Failure Handling Minimums
- REST call failures: retry with jitter for
429/5xx; do not retry4xxbusiness errors blindly. - Webhook ingestion: always return
200after durable enqueue or local persistence. - Idempotency: dedupe by
event_idor (event,event_ts,meeting_uuid) composite key. - Reconciliation: periodic REST poll to repair missed webhook events.
Environment Variables
ZOOM_ACCOUNT_IDZOOM_CLIENT_IDZOOM_CLIENT_SECRETZOOM_HOST_USER_ID(required for S2S meeting creation; do not rely onme)ZOOM_WEBHOOK_SECRETPORT
Official Zoom Sample Repositories
Curated list of official repositories from Zoom for development. Organized by product/SDK.
---
Meeting SDK
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| meetingsdk-web-sample | 643 | Web SDK sample - Component View and Client View |
| meetingsdk-web | 324 | NPM package for embedding meetings |
| meetingsdk-react-sample | 177 | React integration sample |
| meetingsdk-auth-endpoint-sample | 124 | Generate Meeting SDK JWT signatures |
| meetingsdk-angular-sample | 60 | Angular integration sample |
| meetingsdk-vuejs-sample | 42 | Vue.js integration sample |
| meetingsdk-javascript-sample | 41 | Vanilla JavaScript sample |
| meetingsdk-headless-linux-sample | 3 | Headless Linux bot with Docker |
---
Video SDK
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| videosdk-web-sample | 137 | Web Video SDK sample |
| videosdk-web | 56 | NPM package for custom video |
| videosdk-auth-endpoint-sample | 23 | Generate Video SDK JWT signatures |
| videosdk-zoom-ui-toolkit-web | 17 | Prebuilt video chat UI |
| videosdk-zoom-ui-toolkit-react-sample | 17 | UI Toolkit in React |
| videosdk-nextjs-quickstart | 16 | Next.js integration |
| videosdk-zoom-ui-toolkit-javascript-sample | 11 | UI Toolkit in vanilla JS |
| VideoSDK-Web-Telehealth | 11 | Telehealth starter kit |
| videosdk-workshop | 9 | Workshop project |
| videosdk-s3-cloud-recordings | 8 | Auto-upload recordings to S3 |
| videosdk-web-helloworld | 4 | Minimal hello world |
| videosdk-zoom-ui-toolkit-angular-sample | 4 | UI Toolkit in Angular |
| videosdk-zoom-ui-toolkit-vuejs-sample | 3 | UI Toolkit in Vue.js |
| videosdk-vue-nuxt-quickstart | 1 | Vue/Nuxt quickstart |
| videosdk-electron-sample | 1 | Electron sample |
| videosdk-linux-raw-recording-sample | - | Linux headless raw data capture |
---
REST API
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| oauth-sample-app | 91 | Node.js OAuth sample |
| server-to-server-oauth-starter-api | 54 | S2S OAuth starter API |
| api | 44 | API v2 documentation |
| user-level-oauth-starter | 27 | User-level OAuth starter |
| server-to-server-oauth-token | 15 | S2S token generation utility |
| rivet-javascript | 13 | Rivet API library (auth + webhooks + API) |
| websocket-js-sample | 5 | WebSocket connection demo |
| websocket-redis-example | 4 | WebSocket with Redis |
| server-to-server-python-sample | 4 | Python S2S OAuth sample |
| task-manager-sample | 3 | Unified build flow showcase |
| rivet-javascript-sample | 3 | Rivet standup bot sample |
| sample-registration-app | 3 | Webinar registration with rate limits |
---
AI Services
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| ai-services-quickstart | - | Node.js/Express + React playground for Scribe, Summarizer, and Translator |
---
Webhooks
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| webhook-sample | 34 | Receive Zoom webhooks (Node.js) |
| zoom-webhook-verification-headers | - | Custom header auth + webhook validation |
| webhook-to-postgres | 5 | Store webhooks in PostgreSQL |
| Go-Webhooks | - | Go/Fiber webhook listener |
---
Zoom Apps SDK
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| zoomapps-sample-js | 66 | Hello World Zoom App (vanilla JS) |
| zoomapps-advancedsample-react | 55 | Advanced React sample |
| appssdk | 49 | Zoom Apps SDK NPM package |
| zoomapps-texteditor-vuejs | 16 | Collaborate Mode text editor |
| zoomapps-customlayout-js | 16 | Immersive Mode / Layers API |
| zoomapps-workshop-sample | 6 | Getting started workshop |
| zoomapps-serverless-vuejs | 6 | Serverless on Firebase |
| zoomapps-cameramode-vuejs | 6 | Camera Mode + Immersive Mode |
| arlo-meeting-assistant | 2 | RTMS-powered meeting assistant |
| meetingbot-recall-sample | 2 | Meeting bot with Recall.ai + Claude |
---
RTMS (Real-Time Media Streams)
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| zoom-rtms | 29 | Cross-platform RTMS wrapper (Node.js, Python, Go) |
| rtms-samples | 22 | Official RTMS sample apps |
| rtms-developer-preview-js | 3 | Developer preview hello world |
| rtms-sdk-cpp | 2 | C++ RTMS SDK (librtmsdk) |
| rtms-meeting-assistant-starter-kit | 1 | Meeting assistant starter kit |
| rtms-quickstart-js | 1 | Node.js quickstart |
| zoom_rtms_langchain_sample | 1 | LangChain + transcripts for action items |
---
Team Chat & Chatbots
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| unsplash-chatbot | 19 | Send Unsplash photos in Team Chat |
| node.js-chatbot | 18 | Node.js chatbot library |
| vote-chatbot | 10 | Voting bot for Team Chat |
| catbot | 9 | Cat photo bot |
| node.js-chatbot-cli | 8 | Chatbot CLI tool |
| zoom-chatbot-claude-sample | 6 | Anthropic Claude in Team Chat |
| Zoom-Chat-Neural-Search-Assistant-Sample | 2 | Cerebras + Exa search bot |
| zoom-team-chat-shortcut-sample | 1 | Recording management shortcut |
| zoom-teams-chat-snowflake-sample | 1 | Snowflake + Cortex integration |
| zoom-erp-chatbot-sample | 1 | Oracle ERP integration |
| chatbot-nodejs-quickstart | - | Node.js chatbot quickstart |
| chatbot-python-sample | - | Python chatbot with threading |
---
Cobrowse SDK
Official Samples (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| CobrowseSDK-Quickstart | 1 | Cobrowse SDK quickstart |
| cobrowsesdk-auth-endpoint-sample | 2 | JWT generation for Cobrowse |
---
Tooling & Utilities
Official Tools (by Zoom)
| Repository | Stars | Description |
|---|---|---|
| probesdk-web | 3 | Test device/network/server connection |
---
Contributing
Found a useful Zoom official repository missing from this list? Key criteria for inclusion:
- Must be from github.com/zoom
- Demonstrates Zoom SDK/API usage
- Has working code (not just documentation)
Cross-Product Environment Variables (Hub)
Use this file as a normalization map. Product-specific details are maintained in each product skill reference.
Common .env keys
| Variable | Typical products | Where to find |
|---|---|---|
ZOOM_CLIENT_ID | OAuth, REST API, Team Chat, WebSockets, RTMS (OAuth mode), Contact Center APIs | Zoom Marketplace -> your app -> App Credentials |
ZOOM_CLIENT_SECRET | OAuth, REST API, Team Chat, WebSockets, RTMS (OAuth mode), Contact Center APIs | Zoom Marketplace -> your app -> App Credentials |
ZOOM_ACCOUNT_ID | Server-to-Server OAuth flows | Zoom Marketplace -> Server-to-Server OAuth app credentials |
ZOOM_REDIRECT_URI | User-level OAuth apps | Zoom Marketplace -> OAuth redirect/allow list |
ZOOM_WEBHOOK_SECRET / WEBHOOK_SECRET_TOKEN | Webhooks and event validation | Zoom Marketplace -> Event Subscriptions -> Secret Token |
ZOOM_SDK_KEY / ZOOM_SDK_SECRET | Meeting SDK or SDK-based products | Zoom Marketplace -> SDK app credentials |
ZOOM_VIDEO_SDK_KEY / ZOOM_VIDEO_SDK_SECRET | Video SDK and UI Toolkit | Zoom Marketplace -> Video SDK app credentials |
ZOOM_API_KEY / ZOOM_API_SECRET | AI Services Scribe, Summarizer, Translator | Zoom Build Platform API keys area |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN | AI Services batch jobs using S3 | AWS IAM or STS |
PROBE_JS_URL / PROBE_WASM_URL | Probe SDK | Your app/CDN hosted Probe SDK assets (or bundler output paths) |
PROBE_DOMAIN / PROBE_CONNECT_TIMEOUT_MS | Probe SDK | Product policy + Probe SDK diagnostics configuration |
Product references
- ../../zoom-apps-sdk/references/environment-variables.md
- ../../cobrowse-sdk/references/environment-variables.md
- ../../meeting-sdk/references/environment-variables.md
- ../../oauth/references/environment-variables.md
- ../../rest-api/references/environment-variables.md
- ../../rtms/references/environment-variables.md
- ../../team-chat/references/environment-variables.md
- ../../ui-toolkit/references/environment-variables.md
- ../../video-sdk/references/environment-variables.md
- ../../webhooks/references/environment-variables.md
- ../../websockets/references/environment-variables.md
- ../../contact-center/references/environment-variables.md
- ../../phone/references/environment-variables.md
- ../../probe-sdk/references/environment-variables.md
- ../../scribe/references/environment-variables.md
- ../../summarizer/references/environment-variables.md
- ../../translator/references/environment-variables.md
Probe SDK note
- Probe SDK core diagnostics do not require Zoom OAuth/Marketplace credentials.
Interview Answer: Routing with zoom-general
Use zoom-general as the triage layer, then route implementation to specialized skills.
Short answer
1. Classify the query in zoom-general by product intent, platform, and integration pattern. 2. Route to the minimum specialized skills:
- Auth/scopes ->
zoom-oauth - API operations ->
zoom-rest-api - Embedded meetings ->
zoom-meeting-sdk - Custom video experiences ->
zoom-video-sdk - Event delivery ->
zoom-webhooksorzoom-websockets - Live media/transcripts ->
zoom-rtms
3. Execute in sequence: zoom-general -> auth -> core product -> events/media. 4. If ambiguous, ask one disambiguation question before locking the chain.
Canonical guidance and handoff structure:
- Query Routing Playbook