anthropics/zoom-cobrowse-sdk
Reference skill for Zoom Cobrowse SDK. Use after routing to a collaborative-support workflow when implementing browser co-browsing, annotation tools, privacy masking, remote assist, or PIN-based session sharing.
npx skills add https://github.com/anthropics/knowledge-work-plugins --skill zoom-cobrowse-sdk
Background reference for collaborative browsing on the web with Zoom Cobrowse SDK. Use this after the support workflow is clear and you need implementation detail.
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
New to Cobrowse SDK? Follow this path:
Core Concepts:
Features:
Troubleshooting:
Reference:
The Zoom Cobrowse SDK is a JavaScript library that provides:
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.
For customer/agent demos, treat the PIN from customer SDK event pincode_updated as the only user-facing PIN.
If these rules are ignored, agent desk often fails with Pincode is not found / code 30308.
This is the flow most teams implement first, and what users usually expect in demos:
role_type=1)role_type=2)If a demo only has one generic "session" user, it is incomplete for real cobrowse operations.
*.zoom.usNote: Cobrowse SDK is a feature of Video SDK (not a separate product).
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.
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 start
Token endpoint:
// POST https://YOUR_TOKEN_SERVICE_BASE_URL
{
"role": 1, // 1 = customer, 2 = agent
"userId": "user123",
"userName": "John Doe"
}
// Response
{
"token": "eyJhbGciOiJIUzI1NiIs..."
}
<!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>
<!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>
Both customer and agent can draw on the shared screen:
const settings = {
allowAgentAnnotation: true, // Agent can draw
allowCustomerAnnotation: true // Customer can draw
};
Available tools:
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:
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
}
};
Session continues when customer opens new tabs:
const settings = {
multiTabSessionPersistence: {
enable: true,
stateCookieKey: '$$ZCB_SESSION$$' // Cookie key (base64 encoded)
}
};
ZoomCobrowseSDKZoomCobrowseSDK.init(settings, callback)session.start({ sdkToken })pincode_updated event firesagent_joined event firessession.end() or agent leavessession_joined event firesWhen 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.
Problem: Developers often accidentally embed SDK Secret in frontend code.
Solution:
// ❌ 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();
| 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.
| 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 |
| 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 |
Problem: SDK doesn't load on HTTP sites.
Solution:
Problem: Refresh reconnection doesn't work.
Solution: Enable third-party cookies in browser settings.
Affected scenarios:
| 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.
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.
Not synchronized:
Partially synchronized:
Supported:
Not supported:
<img> elements ✗This skill includes comprehensive guides organized by category:
Need help? Start with Integrated Index section below for complete navigation.
_This section was migrated from SKILL.md._
Complete navigation guide for all Cobrowse SDK documentation.
If you're new to Zoom Cobrowse SDK, follow this learning path:
Foundational concepts you need to understand:
Complete working examples for common scenarios:
Complete API and configuration references:
Quick diagnostics and common issue resolution:
Find documentation by what you're trying to do:
Set up cobrowse for the first time:
Add annotation tools:
Hide sensitive data from agents:
Let agents control customer's page:
Use custom PIN codes:
Handle page refreshes:
Integrate with npm (not CDN):
Debug session connection issues:
Configure CORS and CSP headers:
Quick lookup for error code solutions:
External documentation and samples:
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.md
Find by keyword:
Not finding what you need? Check the Official Documentation or ask on the Dev Forum.
.env keys and where to find each value.Take anthropics/zoom-cobrowse-sdk from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm.
Without those the skill loads but fails at the first command.