chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:20:06 +08:00
commit f4548892ad
1138 changed files with 185224 additions and 0 deletions
@@ -0,0 +1,22 @@
{
"name": "zoom-plugin",
"description": "Claude plugin for planning, building, and debugging Zoom integrations across REST APIs, SDKs, webhooks, bots, and MCP workflows",
"version": "1.1.0",
"homepage": "https://developers.zoom.us/",
"repository": "https://github.com/zoom/zoom-plugin",
"license": "MIT",
"keywords": [
"zoom",
"claude-plugin",
"rest-api",
"meeting-sdk",
"video-sdk",
"webhooks",
"oauth",
"mcp"
],
"author": {
"name": "Zoom",
"url": "https://github.com/zoom/zoom-plugin"
}
}
+25
View File
@@ -0,0 +1,25 @@
{
"mcpServers": {
"zoom-mcp": {
"type": "http",
"url": "https://mcp-us.zoom.us/mcp/zoom/streamable",
"headers": {
"Authorization": "Bearer ${ZOOM_MCP_ACCESS_TOKEN}"
}
},
"zoom-docs-mcp": {
"type": "http",
"url": "https://mcp.zoom.us/mcp/docs/streamable",
"headers": {
"Authorization": "Bearer ${ZOOM_DOCS_MCP_ACCESS_TOKEN}"
}
},
"zoom-whiteboard-mcp": {
"type": "http",
"url": "https://mcp-us.zoom.us/mcp/whiteboard/streamable",
"headers": {
"Authorization": "Bearer ${ZOOM_WHITEBOARD_MCP_ACCESS_TOKEN}"
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
# Zoom Plugin
Cross-platform discovery file for agent tools that look for `AGENTS.md`.
## What This Repo Provides
This repository contains a Zoom developer plugin centered on `SKILL.md`-based workflows and reference material.
Primary capabilities:
- choose the right Zoom surface for a use case
- plan Zoom integrations across REST APIs, SDKs, webhooks, OAuth, and MCP
- debug broken Zoom integrations
- build focused Zoom implementations for meetings, bots, chat, phone, contact center, and virtual agent workflows
- provide deep product-specific reference material under `skills/`
## Primary Entry Skills
- `skills/start/SKILL.md` — default routing entry point
- `skills/plan-zoom-product/SKILL.md` — pick the right Zoom developer product
- `skills/plan-zoom-integration/SKILL.md` — turn an idea into an implementation plan
- `skills/setup-zoom-oauth/SKILL.md` — choose the auth model and redirect flow
- `skills/build-zoom-meeting-app/SKILL.md` — implement an embedded or managed meeting app
- `skills/build-zoom-bot/SKILL.md` — implement a meeting bot or recorder
- `skills/debug-zoom/SKILL.md` — isolate the failing integration layer
- `skills/setup-zoom-mcp/SKILL.md` — plan a Zoom MCP workflow for Claude
## Repo Shape
- `.claude-plugin/plugin.json` — Claude plugin manifest
- `.mcp.json` — bundled Zoom MCP server definition
- `skills/` — all plugin skills and supporting references
- `README.md` — user-facing overview
- `CONNECTORS.md` — bundled MCP connector notes
## Usage Notes
- For Claude Code, install or load this as a plugin.
- For other agent ecosystems, treat the `skills/` tree as the primary reusable asset.
- Workflow skills are the front door; product-specific folders under `skills/` are supporting references.
+12
View File
@@ -0,0 +1,12 @@
# Changelog
All notable changes to this plugin are documented in this file.
## Unreleased
- aligned the repository with the current Claude plugin structure around `.claude-plugin/plugin.json`, `skills/`, and `.mcp.json`
- added Claude-facing installation and connector documentation
- converted command-style workflows into `SKILL.md`-based workflows under `skills/`
- bundled the main Zoom MCP server configuration in `.mcp.json`
- removed the Whiteboard MCP server from the bundled plugin surface
- tightened skill metadata and reduced maintainer-facing wording in user-facing docs
+49
View File
@@ -0,0 +1,49 @@
# Connectors
This plugin works in two modes:
- Standalone: Claude uses the bundled Zoom skills and reference material included with this plugin.
- Supercharged: Claude can also use the bundled Zoom MCP servers from [`.mcp.json`](./.mcp.json) for live tool access.
## Included MCP Servers
| Connector | Endpoint | Use For |
|---|---|---|
| `zoom-mcp` | `https://mcp-us.zoom.us/mcp/zoom/streamable` | Zoom-hosted MCP workflows for meetings, recordings, summaries, and meeting assets |
| `zoom-docs-mcp` | `https://mcp.zoom.us/mcp/docs/streamable` | Zoom Docs creation, retrieval, and Markdown-based document workflows |
| `zoom-whiteboard-mcp` | `https://mcp-us.zoom.us/mcp/whiteboard/streamable` | Whiteboard-specific MCP workflows |
## Authentication
The bundled MCP definitions expect bearer tokens in these environment variables:
```bash
export ZOOM_MCP_ACCESS_TOKEN="your_zoom_user_oauth_access_token"
export ZOOM_DOCS_MCP_ACCESS_TOKEN="your_zoom_docs_mcp_access_token"
export ZOOM_WHITEBOARD_MCP_ACCESS_TOKEN="your_zoom_user_oauth_access_token"
```
- `ZOOM_MCP_ACCESS_TOKEN` is used for the main Zoom MCP server.
- `ZOOM_DOCS_MCP_ACCESS_TOKEN` is used for the Zoom Docs MCP server.
- `ZOOM_WHITEBOARD_MCP_ACCESS_TOKEN` is used for the Whiteboard MCP server.
- If one OAuth token includes both the main Zoom MCP scopes and the Zoom Docs MCP scopes, both variables can use the same value.
- After setting or rotating any of these tokens, restart Claude Code or re-enable the plugin so the MCP servers restart with the new environment.
## What You Can Do Without Connectors
- Choose the right Zoom surface for a new integration
- Plan SDK, REST API, webhook, OAuth, and MCP implementations
- Compare Meeting SDK vs Video SDK vs Zoom Apps vs REST API
- Debug architecture, auth, event-delivery, and integration mistakes
- Use the deep Zoom reference library bundled in `skills/`
## What Connectors Add
- Live MCP tool discovery and execution against Zoom-hosted MCP servers
- Real meeting-search, recording-resource, and document workflows
- Whiteboard-specific tool access when applicable
## Notes
- If a command or skill mentions connectors and you are not connected, continue in standalone mode using the reference docs.
- If you are unsure which connector is relevant, start with [`/setup-zoom-mcp`](./skills/setup-zoom-mcp/SKILL.md).
+186
View File
@@ -0,0 +1,186 @@
# Contributing to Zoom Developer Platform Agent Skills
Thank you for your interest in contributing! This document provides guidelines for contributing to these Agent Skills.
## Ways to Contribute
You can contribute in any of these ways:
1. Submit a pull request with improvements.
2. Raise an issue on GitHub for bugs, gaps, or enhancement ideas.
3. Reach out on the [Zoom Developer Forum](https://devforum.zoom.us/) with feedback and improvement suggestions for these agent skills.
### 1. Report Issues
- Documentation errors or outdated information
- Missing use cases or scenarios
- Incorrect code examples
### 2. Improve Documentation
- Fix typos and clarify explanations
- Add missing code examples
- Update for new SDK versions
### 3. Add New Skills
- New use cases
- New platform coverage
- New integration patterns
## Contribution Process
### For Small Changes (typos, clarifications)
1. Fork the repository
2. Make your changes
3. Submit a pull request with a clear description
### For Larger Changes (new use cases, skills)
1. Open an issue first to discuss the proposed change
2. Fork the repository
3. Create a feature branch
4. Follow the skill format guidelines below
5. Submit a pull request
## Skill Format Guidelines
### SKILL.md Structure
```markdown
---
name: skill-name
description: |
Brief description (1-3 sentences).
Include when to use this skill.
---
# Skill Title
[Content following the template in PLAN.md]
```
### Guidelines
1. **Keep SKILL.md under 500 lines** - Move details to `references/`
2. **Max 3 directory levels** - `skill/references/file.md`
3. **Include code examples** - Real, working code developers can use
4. **Document gotchas** - Common mistakes and limitations
5. **Link to official sources** - Prefer Zoom documentation
### Maintenance Checklist
Use this checklist before merging documentation or skill changes:
1. Confirm you are editing the correct skill or product folder.
2. Keep `SKILL.md` as the entrypoint in every skill directory.
3. If examples include credentials, reference `.env` keys rather than hardcoded values.
4. Never commit machine-local absolute paths or machine-specific endpoints.
5. After moving or renaming docs, update cross-links from the relevant parent `SKILL.md` files.
6. Verify frontmatter stays accurate: `name`, `description`, and any optional fields such as `triggers`, `argument-hint`, or `user-invocable`.
7. Remove dead links and stale product claims after any refactor or version update.
8. Make sure every new markdown file is reachable from at least one parent navigation file.
9. Track deprecations and renames explicitly so future updates remain migration-safe.
### Repository Naming Conventions
- Keep canonical skill folder names aligned with [skills/start/SKILL.md](skills/start/SKILL.md).
- Current canonical folders include:
- `general`, `rest-api`, `webhooks`, `websockets`, `meeting-sdk`, `video-sdk`, `zoom-apps-sdk`
- `rtms`, `team-chat`, `ui-toolkit`, `cobrowse-sdk`, `oauth`, `zoom-mcp`
- `contact-center`, `virtual-agent`, `phone`, `rivet-sdk`, `probe-sdk`
### Markdown Linking Rules (Required)
- Use real markdown links for local docs (for example: `text -> docs/example.md`).
- Do not use backticks for local doc references if you want them counted in relationship graphs.
- Use repository-relative paths; do not commit machine-local absolute paths (for example `/home/your-user/...`).
- Every new `.md` file should be linked from at least one parent/index/`SKILL.md` file.
## Using Claude for Contributions
You can use Claude (or other AI assistants) to help create or improve skills:
### Recommended Workflow
1. **Research Phase**
```
Research the official Zoom documentation for [topic].
Check the developer forum for common issues.
Find working code examples.
```
2. **Drafting Phase**
```
Create a skill following the SKILL.md template.
Include practical code examples.
Document known limitations and gotchas.
```
3. **Validation Phase**
```
Cross-check all information with official Zoom docs.
Verify code examples are syntactically correct.
Ensure links are valid.
```
### Claude-Specific Tips
- **Be specific**: "Create a use case for RTMS audio streaming to S3" not "write about RTMS"
- **Provide context**: Share relevant existing skills as examples
- **Iterate**: Review drafts and ask for improvements
- **Verify**: Always cross-check AI-generated content with official sources
### What Claude Can Help With
| Task | How Claude Helps |
|------|------------------|
| Research | Search docs, forums, GitHub for information |
| Drafting | Create initial skill content following templates |
| Code examples | Generate working code snippets |
| Cross-referencing | Check consistency across skills |
| Formatting | Ensure markdown is correct |
### What Requires Human Review
| Task | Why Human Review |
|------|------------------|
| Technical accuracy | AI may hallucinate APIs or features |
| Real-world gotchas | Comes from actual development experience |
| Business logic | Zoom-specific requirements and policies |
| Security practices | Must be verified against official guidance |
## Quality Standards
### Do
- Verify all claims with official documentation
- Include working, tested code examples
- Document known limitations prominently
- Link to official resources
- Keep examples simple and practical
- Check that moved or renamed docs still have inbound links
- Remove outdated guidance that no longer matches the current plugin structure
### Don't
- Include unverified information
- Speculate about undocumented behavior
- Copy proprietary code without permission
- Include outdated or deprecated APIs without noting it
- Over-engineer examples
## Code of Conduct
- Be respectful and constructive
- Focus on improving the documentation
- Credit sources appropriately
- Follow Zoom's developer terms of service
## Questions?
- Open a GitHub issue for questions about contributing
- Check existing issues before creating new ones
- Join the [Zoom Developer Forum](https://devforum.zoom.us/) for Zoom-specific questions, feedback, and improvement requests for these agent skills
## License
By contributing, you agree that your contributions will be licensed under the MIT License.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Zoom Video Communications, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+122
View File
@@ -0,0 +1,122 @@
# Zoom Plugin
A Claude plugin for planning, building, and debugging Zoom integrations. It helps choose the right Zoom surface, shape implementations, debug failures, and route into the right Zoom references without making the user read the whole doc tree first.
## Installation
Install this directory as a local Claude plugin. The plugin manifest is at [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json) and the bundled Zoom MCP connectors are defined in [`.mcp.json`](.mcp.json).
Before using the bundled MCP servers, export bearer tokens for the Zoom surfaces you want Claude to use:
```bash
export ZOOM_MCP_ACCESS_TOKEN="your_zoom_user_oauth_access_token"
export ZOOM_DOCS_MCP_ACCESS_TOKEN="your_zoom_docs_mcp_access_token"
export ZOOM_WHITEBOARD_MCP_ACCESS_TOKEN="your_zoom_user_oauth_access_token"
```
## Slash Workflows
Explicit slash workflows implemented as skills under `skills/`:
| Workflow | Description |
|---|---|
| [`/start`](skills/start/SKILL.md) | Start with a Zoom app idea and get routed to the right product and build path |
| [`/setup-zoom-oauth`](skills/setup-zoom-oauth/SKILL.md) | Choose the auth model, scopes, and redirect flow for a Zoom app |
| [`/build-zoom-meeting-app`](skills/build-zoom-meeting-app/SKILL.md) | Build an embedded or managed Zoom meeting flow |
| [`/build-zoom-bot`](skills/build-zoom-bot/SKILL.md) | Build bots, recorders, and real-time meeting processors |
| [`/debug-zoom`](skills/debug-zoom/SKILL.md) | Triage a broken Zoom integration and isolate the failing layer |
| [`/setup-zoom-mcp`](skills/setup-zoom-mcp/SKILL.md) | Decide when Zoom MCP fits and set up a safe Claude workflow |
| [`/build-zoom-rest-api-app`](skills/rest-api/SKILL.md) | Route into Zoom REST endpoints, scopes, and resource patterns |
| [`/build-zoom-meeting-sdk-app`](skills/meeting-sdk/SKILL.md) | Route into embedded Zoom meeting implementation details |
| [`/build-zoom-video-sdk-app`](skills/video-sdk/SKILL.md) | Route into custom video-session implementation details |
| [`/setup-zoom-webhooks`](skills/webhooks/SKILL.md) | Set up Zoom webhook subscriptions, signature verification, and handlers |
| [`/setup-zoom-websockets`](skills/websockets/SKILL.md) | Set up Zoom WebSocket event delivery when it fits better than webhooks |
| [`/build-zoom-team-chat-app`](skills/team-chat/SKILL.md) | Build Team Chat user or chatbot integrations |
| [`/build-zoom-phone-integration`](skills/phone/SKILL.md) | Build Zoom Phone integrations around Smart Embed, APIs, and events |
| [`/build-zoom-contact-center-app`](skills/contact-center/SKILL.md) | Build Contact Center app, web, or native integrations |
| [`/build-zoom-virtual-agent`](skills/virtual-agent/SKILL.md) | Build Virtual Agent web or mobile wrapper integrations |
## Internal Routing Skills
These remain in the plugin as automatic routing helpers, but they are no longer part of the public slash-command surface:
- [`start`](skills/start/SKILL.md)
- [`plan-zoom-product`](skills/plan-zoom-product/SKILL.md)
- [`plan-zoom-integration`](skills/plan-zoom-integration/SKILL.md)
- [`choose-zoom-approach`](skills/choose-zoom-approach/SKILL.md)
- [`design-mcp-workflow`](skills/design-mcp-workflow/SKILL.md)
- [`debug-zoom-integration`](skills/debug-zoom-integration/SKILL.md)
## Deep References
The plugin also keeps the original Zoom product-specific reference library under `skills/`. These are supporting references, not the primary entry surface:
- [`skills/general/`](skills/general/)
- [`skills/rest-api/`](skills/rest-api/)
- [`skills/meeting-sdk/`](skills/meeting-sdk/)
- [`skills/video-sdk/`](skills/video-sdk/)
- [`skills/webhooks/`](skills/webhooks/)
- [`skills/websockets/`](skills/websockets/)
- [`skills/oauth/`](skills/oauth/)
- [`skills/zoom-mcp/`](skills/zoom-mcp/)
## Example Workflows
### Starting from a Zoom app idea
```text
/start Build an internal meeting assistant that joins calls, extracts action items, and stores summaries
```
### Planning a new app
```text
/start Build a React app that lets customers schedule and join Zoom meetings from our product
```
### Debugging a broken webhook
```text
/debug-zoom My Zoom webhook signature verification fails in production but not locally
```
### Designing an MCP flow
```text
/setup-zoom-mcp I want Claude to search meetings, pull recording resources, and create follow-up docs
```
## Connectors
See [CONNECTORS.md](CONNECTORS.md). The plugin works standalone from the bundled skills, and gets supercharged when Claude can use the bundled Zoom MCP servers from [`.mcp.json`](.mcp.json).
## Cross-Platform Notes
This repo is packaged first as a Claude plugin, but it also includes [AGENTS.md](AGENTS.md) for agent ecosystems that use a repo-level discovery file. The reusable core remains the `skills/` tree and its `SKILL.md` files.
## Structure
```text
Zoom Plugin/
├── .claude-plugin/plugin.json
├── .mcp.json
├── CONNECTORS.md
├── skills/
│ ├── plan-zoom-product/
│ ├── plan-zoom-integration/
│ ├── debug-zoom/
│ ├── setup-zoom-mcp/
│ ├── start/
│ ├── choose-zoom-approach/
│ ├── setup-zoom-oauth/
│ ├── build-zoom-meeting-app/
│ ├── build-zoom-bot/
│ ├── design-mcp-workflow/
│ ├── debug-zoom-integration/
│ └── ... existing Zoom reference skills
└── README.md
```
## License
MIT
@@ -0,0 +1,37 @@
---
name: build-zoom-bot
description: Build a Zoom meeting bot, recorder, or real-time media workflow. Use when joining meetings programmatically, processing live media or transcripts, or combining Meeting SDK, RTMS, and backend services.
---
# /build-zoom-bot
Use this skill for automation that joins meetings, captures media, or reacts to live session data.
## Covers
- Bot architecture
- Meeting join strategy
- Real-time media and transcript handling
- Backend orchestration
- Storage, post-processing, and event flow design
## Workflow
1. Clarify whether the bot needs to join, observe, transcribe, summarize, or act.
2. Route to Meeting SDK and RTMS as the core implementation path.
3. Add REST API for meeting/resource management and Webhooks for asynchronous events when needed.
4. Call out environment and lifecycle constraints early.
## Primary References
- [meeting-sdk](../meeting-sdk/SKILL.md)
- [rtms](../rtms/SKILL.md)
- [scribe](../scribe/SKILL.md)
- [rest-api](../rest-api/SKILL.md)
- [webhooks](../webhooks/SKILL.md)
## Common Mistakes
- Treating batch transcription and live media as the same workflow
- Designing the bot before defining join authority and auth model
- Forgetting post-meeting storage and retry behavior
@@ -0,0 +1,38 @@
---
name: build-zoom-meeting-app
description: Build or embed a Zoom meeting flow. Use when implementing Meeting SDK joins, web or mobile meeting embeds, meeting lifecycle flows, or when deciding between Meeting SDK and Video SDK.
---
# /build-zoom-meeting-app
Use this skill for embedded meeting experiences and meeting lifecycle implementation.
## Covers
- Meeting SDK selection and platform routing
- Join/auth implementation planning
- Meeting creation plus join flow design
- Web vs native platform considerations
- Meeting SDK vs Video SDK boundary decisions
## Workflow
1. Confirm whether the user wants a Zoom meeting or a custom video session.
2. Route to Meeting SDK if the user needs actual Zoom meetings.
3. Pull in the relevant platform references.
4. Add REST API only for meeting creation, resource management, or reporting.
5. Add webhooks or RTMS only when the use case explicitly needs them.
## Primary References
- [meeting-sdk](../meeting-sdk/SKILL.md)
- [rest-api](../rest-api/SKILL.md)
- [webhooks](../webhooks/SKILL.md)
- [rtms](../rtms/SKILL.md)
- [video-sdk](../video-sdk/SKILL.md)
## Common Mistakes
- Using Video SDK for normal Zoom meeting embeds
- Mixing resource-management APIs into the core join flow without reason
- Skipping platform-specific SDK constraints until too late
@@ -0,0 +1,37 @@
---
name: choose-zoom-approach
description: Choose the right Zoom architecture for a use case. Use when deciding between REST API, Webhooks, WebSockets, Meeting SDK, Video SDK, Zoom Apps SDK, Zoom MCP, Phone, Contact Center, or a hybrid approach.
user-invocable: false
---
# Choose Zoom Approach
Pick the smallest correct Zoom surface for the job, then layer in only the supporting pieces that are actually required.
## Decision Framework
| Problem Type | Primary Zoom Surface |
|---|---|
| Deterministic backend automation, account management, reporting, scheduled jobs | [rest-api](../rest-api/SKILL.md) |
| Event delivery to your backend | [webhooks](../webhooks/SKILL.md) or [websockets](../websockets/SKILL.md) |
| Embed Zoom meetings into your app | [meeting-sdk](../meeting-sdk/SKILL.md) |
| Build a fully custom video experience | [video-sdk](../video-sdk/SKILL.md) |
| Build inside the Zoom client | [zoom-apps-sdk](../zoom-apps-sdk/SKILL.md) |
| AI-agent tool workflows over Zoom data | [zoom-mcp](../zoom-mcp/SKILL.md) |
| Real-time media extraction or meeting bots | [rtms](../rtms/SKILL.md) plus [meeting-sdk](../meeting-sdk/SKILL.md) when needed |
| Phone workflows | [phone](../phone/SKILL.md) |
| Contact Center or Virtual Agent flows | [contact-center](../contact-center/SKILL.md) or [virtual-agent](../virtual-agent/SKILL.md) |
## Guardrails
- Do not recommend Video SDK when the user actually needs Zoom meeting semantics.
- Do not recommend Meeting SDK when the user needs a fully custom session product.
- Do not replace deterministic backend automation with MCP-only guidance.
- Prefer hybrid `rest-api + zoom-mcp` when the user needs both stable system actions and AI-driven discovery.
## What To Produce
- One recommended path
- Minimum supporting components
- Hard constraints and tradeoffs
- Immediate next implementation step
@@ -0,0 +1,79 @@
# 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.md` is 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_updated` as 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
```bash
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.
@@ -0,0 +1,894 @@
---
name: zoom-cobrowse-sdk
description: "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."
user-invocable: false
triggers:
- "cobrowse"
- "co-browse"
- "collaborative browsing"
- "agent assist"
- "customer support screen share"
- "zoom cobrowse"
---
# Zoom Cobrowse SDK - Web Development
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
## 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](troubleshooting/cors-csp.md))
- **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](https://marketplace.zoom.us/)
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:
```bash
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:**
```javascript
// 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)
```html
<!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)
```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>
```
### 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:
```javascript
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:
```javascript
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:
```javascript
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:
```javascript
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:
```javascript
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)
```javascript
// ❌ 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:
```html
<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](get-started.md)
- [JWT Authentication](concepts/jwt-authentication.md)
- [Customer Integration](examples/customer-integration.md)
- [Agent Integration](examples/agent-integration.md)
**Add annotation tools:**
- [Annotation Tools Example](examples/annotations.md)
- [Settings Reference - allowAgentAnnotation](references/settings-reference.md#allowa gentannotation)
- [Settings Reference - allowCustomerAnnotation](references/settings-reference.md#allowcustomerannotation)
**Hide sensitive data from agents:**
- [Privacy Masking Example](examples/privacy-masking.md)
- [Settings Reference - piiMask](references/settings-reference.md#piimask)
**Let agents control customer's page:**
- [Remote Assist Example](examples/remote-assist.md)
- [Settings Reference - remoteAssist](references/settings-reference.md#remoteassist)
**Use custom PIN codes:**
- [BYOP Custom PIN Example](examples/byop-custom-pin.md)
- [JWT Authentication - enable_byop](concepts/jwt-authentication.md#enable-byop)
**Handle page refreshes:**
- [Auto-Reconnection Example](examples/auto-reconnection.md)
- [Session Lifecycle - Recovery](concepts/session-lifecycle.md#session-recovery)
**Integrate with npm (not CDN):**
- [BYOP Custom PIN Example](examples/byop-custom-pin.md)
- [Distribution Methods](concepts/distribution-methods.md#npm-integration)
**Debug session connection issues:**
- [Common Issues](troubleshooting/common-issues.md)
- [Error Codes](troubleshooting/error-codes.md)
- [Session Events - session_error](examples/session-events.md#session-error)
**Configure CORS and CSP headers:**
- [CORS and CSP Guide](troubleshooting/cors-csp.md)
- [Browser Compatibility](troubleshooting/browser-compatibility.md)
## By Error Code
Quick lookup for error code solutions:
### Session Errors
- **1001** (SESSION_START_FAILED) → [Error Codes](troubleshooting/error-codes.md#1001-session-start-failed)
- **1002** (SESSION_CONNECTING_IN_PROGRESS) → [Error Codes](troubleshooting/error-codes.md#1002-session-connecting-in-progress)
- **1004** (SESSION_COUNT_LIMIT) → [Error Codes](troubleshooting/error-codes.md#1004-session-count-limit)
- **1011** (SESSION_JOIN_FAILED) → [Error Codes](troubleshooting/error-codes.md#1011-session-join-failed)
- **1012** (SESSION_CUSTOMER_COUNT_LIMIT) → [Error Codes](troubleshooting/error-codes.md#1012-session-customer-count-limit)
- **1013** (SESSION_AGENT_COUNT_LIMIT) → [Error Codes](troubleshooting/error-codes.md#1013-session-agent-count-limit)
- **1015** (SESSION_DUPLICATE_USER) → [Error Codes](troubleshooting/error-codes.md#1015-session-duplicate-user)
- **1016** (NETWORK_ERROR) → [Error Codes](troubleshooting/error-codes.md#1016-network-error)
- **1017** (SESSION_CANCELING_IN_PROGRESS) → [Error Codes](troubleshooting/error-codes.md#1017-session-canceling-in-progress)
### PIN Errors
- **1006** (SESSION_JOIN_PIN_NOT_FOUND) → [Error Codes](troubleshooting/error-codes.md#1006-session-join-pin-not-found)
- **1008** (SESSION_PIN_INVALID_FORMAT) → [Error Codes](troubleshooting/error-codes.md#1008-session-pin-invalid-format)
- **1009** (SESSION_START_PIN_REQUIRED) → [Error Codes](troubleshooting/error-codes.md#1009-session-start-pin-required)
- **1010** (SESSION_START_PIN_CONFLICT) → [Error Codes](troubleshooting/error-codes.md#1010-session-start-pin-conflict)
### Auth Errors
- **2001** (TOKEN_INVALID) → [Error Codes](troubleshooting/error-codes.md#2001-token-invalid)
### Service Errors
- **9999** (UNDEFINED) → [Error Codes](troubleshooting/error-codes.md#9999-undefined)
## 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.md
```
## Search Tips
**Find by keyword:**
- "annotation" → [Annotation Tools](examples/annotations.md)
- "mask" or "privacy" → [Privacy Masking](examples/privacy-masking.md)
- "PIN" or "custom PIN" → [BYOP Custom PIN](examples/byop-custom-pin.md)
- "JWT" or "token" → [JWT Authentication](concepts/jwt-authentication.md)
- "error" → [Error Codes](troubleshooting/error-codes.md)
- "CORS" or "CSP" → [CORS and CSP](troubleshooting/cors-csp.md)
- "iframe" → [Agent Integration](examples/agent-integration.md)
- "npm" → [Distribution Methods](concepts/distribution-methods.md), [BYOP](examples/byop-custom-pin.md)
- "refresh" or "reconnect" → [Auto-Reconnection](examples/auto-reconnection.md)
- "agent" → [Agent Integration](examples/agent-integration.md), [Two Roles Pattern](concepts/two-roles-pattern.md)
- "customer" → [Customer Integration](examples/customer-integration.md), [Two Roles Pattern](concepts/two-roles-pattern.md)
---
**Not finding what you need?** Check the [Official Documentation](https://developers.zoom.us/docs/cobrowse-sdk/) or ask on the [Dev Forum](https://devforum.zoom.us/).
## Environment Variables
- See [references/environment-variables.md](references/environment-variables.md) for standardized `.env` keys and where to find each value.
@@ -0,0 +1,13 @@
# 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](../get-started.md)
- [Features (official)](../references/features-official.md)
@@ -0,0 +1,13 @@
# 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)](../references/authorization-official.md)
- [Get Started](../references/get-started-official.md)
@@ -0,0 +1,13 @@
# 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](../get-started.md)
- [Features (official)](../references/features-official.md)
@@ -0,0 +1,43 @@
# Two Roles Pattern
Zoom Cobrowse uses two roles:
- `role_type=1`: customer session
- `role_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 + PIN
- `POST /api/agent/connect` -> validate PIN + issue agent token
- `POST /api/session/revoke` -> end session
- `GET /api/session/list` -> operational visibility
See:
- [Get Started](../get-started.md)
- [Authorization (official)](../references/authorization-official.md)
@@ -0,0 +1,7 @@
# Agent Integration
Agent integration joins an active customer session by PIN using an agent-role token.
See:
- [Get Started](../get-started.md)
- [Authorization (official)](../references/authorization-official.md)
@@ -0,0 +1,7 @@
# Annotation Tools
Enable annotation settings during SDK initialization to allow drawing and highlighting.
See:
- [Features (official)](../references/features-official.md)
- [API (official)](../references/api-official.md)
@@ -0,0 +1,7 @@
# Auto-Reconnection
Implement reconnection handlers for transient network interruptions and refresh scenarios.
See:
- [Get Started](../get-started.md)
- [Features (official)](../references/features-official.md)
@@ -0,0 +1,7 @@
# BYOP Custom PIN
Bring Your Own PIN mode lets you control PIN generation/format in your own application flow.
See:
- [Authorization (official)](../references/authorization-official.md)
- [Get Started](../get-started.md)
@@ -0,0 +1,7 @@
# Customer Integration
Customer-side integration should initialize the SDK, fetch a server-generated token, then start a session.
See:
- [Get Started](../get-started.md)
- [API (official)](../references/api-official.md)
@@ -0,0 +1,7 @@
# Multi-Tab Persistence
Cobrowse sessions can continue across tabs when configured correctly and browser constraints are met.
See:
- [Features (official)](../references/features-official.md)
- [Get Started](../get-started.md)
@@ -0,0 +1,7 @@
# Privacy Masking
Configure masking selectors for sensitive customer fields so agents cannot view protected values.
See:
- [Features (official)](../references/features-official.md)
- [Get Started](../get-started.md)
@@ -0,0 +1,7 @@
# Remote Assist
Remote assist allows approved agent interactions (for example, scrolling) during active sessions.
See:
- [Features (official)](../references/features-official.md)
- [API (official)](../references/api-official.md)
@@ -0,0 +1,7 @@
# Session Events
Use SDK session events to track lifecycle transitions and update your UI accordingly.
See:
- [API (official)](../references/api-official.md)
- [Features (official)](../references/features-official.md)
@@ -0,0 +1,554 @@
# 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](https://developers.zoom.us/docs/build/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:
```json
{
"alg": "HS256",
"typ": "JWT"
}
```
The payload differs by role:
**Customer JWT payload** (role_type=1):
```json
{
"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):
```json
{
"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):
```javascript
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:
```bash
# 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 start
```
The server will run on the base URL you configure for your token service.
**Token Request:**
```javascript
// 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](concepts/jwt-authentication.md)
## 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:
```html
<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
```javascript
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
```javascript
// 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
```html
<!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
```html
<!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 media
- `camera *` - Camera access
- `microphone *` - Microphone access
- `display-capture *` - Screen capture
- `geolocation *` - 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:
```javascript
const settings = {
allowAgentAnnotation: true, // Agent can draw
allowCustomerAnnotation: true // Customer can draw
};
```
**See**: [Annotation Tools Example](examples/annotations.md)
### Data Masking
Hide sensitive fields from agents:
```javascript
const settings = {
piiMask: {
maskType: 'custom_input',
maskCssSelectors: '.sensitive-field, #ssn, #credit-card',
maskHTMLAttributes: 'data-sensitive=true'
}
};
```
**See**: [Privacy Masking Example](examples/privacy-masking.md)
### Remote Assist
Allow agent to scroll the customer's page:
```javascript
const settings = {
remoteAssist: {
enable: true,
enableCustomerConsent: true, // Customer must approve
remoteAssistTypes: ['scroll_page']
}
};
```
**See**: [Remote Assist Example](examples/remote-assist.md)
### Bring Your Own PIN (BYOP)
Use custom PIN codes instead of auto-generated ones:
1. Enable BYOP in JWT payload:
```json
{
"enable_byop": 1,
...
}
```
2. Provide custom PIN when starting session:
```javascript
session.start({
customPinCode: 'MYPIN123',
sdkToken: token
});
```
**See**: [BYOP Custom PIN Example](examples/byop-custom-pin.md)
## Next Steps
- **Learn core concepts**: [Session Lifecycle](concepts/session-lifecycle.md)
- **Explore features**: [Complete documentation index](SKILL.md)
- **Handle errors**: [Error Codes Reference](troubleshooting/error-codes.md)
- **Production checklist**: [CORS and CSP Configuration](troubleshooting/cors-csp.md)
## 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)](examples/byop-custom-pin.md) 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.
@@ -0,0 +1,104 @@
# Cobrowse SDK - API Reference
SDK methods and events.
## Initialization
```javascript
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.
```javascript
const session = await cobrowse.startSession();
// Returns: { pin: string, sessionId: string }
```
### endSession()
End the current session.
```javascript
await cobrowse.endSession();
```
### pause()
Pause screen sharing.
```javascript
cobrowse.pause();
```
### resume()
Resume screen sharing.
```javascript
cobrowse.resume();
```
## Events
### sessionStarted
```javascript
cobrowse.on('sessionStarted', (session) => {
// session.pin - PIN for agent to join
// session.sessionId - Unique session ID
});
```
### agentJoined
```javascript
cobrowse.on('agentJoined', (agent) => {
// agent.name - Agent display name
// agent.userId - Agent user ID
});
```
### agentLeft
```javascript
cobrowse.on('agentLeft', (agent) => {
// Agent disconnected
});
```
### sessionEnded
```javascript
cobrowse.on('sessionEnded', () => {
// Session terminated
});
```
### error
```javascript
cobrowse.on('error', (error) => {
// error.code - Error code
// error.message - Error description
});
```
## Resources
- **SDK Reference**: https://developers.zoom.us/docs/cobrowse-sdk/sdk-reference/
@@ -0,0 +1,5 @@
# API Reference
This local reference points to the official Cobrowse API docs.
- [API (official)](api-official.md)
@@ -0,0 +1,5 @@
# API (Reference)
Canonical source:
- [API (official)](api-official.md)
@@ -0,0 +1,90 @@
# Cobrowse SDK - Authorization
JWT authentication for Cobrowse sessions.
## Overview
Both customers and agents require JWTs for authentication. Generate tokens server-side.
## JWT Structure
### Header
```json
{
"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` (not `user_identity`)
- `app_key`
- `role_type`
- `user_name`
- `iat`
- `exp`
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
```javascript
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
```javascript
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/
@@ -0,0 +1,5 @@
# Authorization (Reference)
Canonical source:
- [Authorization (official)](authorization-official.md)
@@ -0,0 +1,20 @@
# 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_SECRET` server-side.
- Some samples use aliases (`SDK_KEY`, `SDK_SECRET`); normalize internally.
@@ -0,0 +1,6 @@
# Error Codes
Use the official Cobrowse docs and API behavior notes for code-level troubleshooting.
- [Get Started (official)](get-started-official.md)
- [API (official)](api-official.md)
@@ -0,0 +1,111 @@
# 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
```javascript
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
```html
<!-- 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
```javascript
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.
```javascript
const cobrowse = new ZoomCobrowse({
sdkKey: SDK_KEY,
token: token,
byop: {
enabled: true,
pin: 'YOUR_CUSTOM_PIN'
}
});
```
## Session Control
### End Session
```javascript
cobrowse.endSession();
```
### Pause/Resume
```javascript
cobrowse.pause();
cobrowse.resume();
```
### Events
```javascript
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/
@@ -0,0 +1,5 @@
# Features (Reference)
Canonical source:
- [Features (official)](features-official.md)
@@ -0,0 +1,91 @@
# 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.
```javascript
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:
```html
<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:
```html
<iframe
src="https://cobrowse.zoom.us/agent?pin={PIN}"
allow="camera; microphone"
></iframe>
```
## Next Steps
- Configure [privacy masking](features.md#masking)
- Set up [annotations](features.md#annotations)
- Implement [Bring Your Own PIN](features.md#byop)
## Resources
- **Cobrowse docs**: https://developers.zoom.us/docs/cobrowse-sdk/get-started/
@@ -0,0 +1,5 @@
# Get Started (Reference)
Canonical source:
- [Get Started (official)](get-started-official.md)
@@ -0,0 +1,5 @@
# Session Events Reference
Event names and payload behavior are covered in official Cobrowse API documentation.
- [API (official)](api-official.md)
@@ -0,0 +1,6 @@
# Settings Reference
Initialization and runtime settings are documented in the official Cobrowse references.
- [Features (official)](features-official.md)
- [API (official)](api-official.md)
@@ -0,0 +1,7 @@
# Browser Compatibility
Validate your supported browser matrix and test privacy/cookie constraints that may affect sessions.
See:
- [Features (official)](../references/features-official.md)
- [Get Started](../get-started.md)
@@ -0,0 +1,58 @@
# 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.md)
- [Get Started (official)](../references/get-started-official.md)
@@ -0,0 +1,11 @@
# 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.md)
- [Get Started (official)](../references/get-started-official.md)
@@ -0,0 +1,7 @@
# Error Codes Troubleshooting
Use official API guidance and startup diagnostics to map error behavior.
See:
- [Error Codes Reference](../references/error-codes.md)
- [API (official)](../references/api-official.md)
@@ -0,0 +1,73 @@
# 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.md` is 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
- `entryId` for chat/video/ZVA channels.
- `apiKey` for 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.) to `ZoomCCInterface`.
## 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 of `onService: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 `config` capabilities 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.
@@ -0,0 +1,121 @@
---
name: build-zoom-contact-center-app
description: "Reference skill for Zoom Contact Center. Use after routing to a contact-center workflow when implementing app, web, or native integrations; engagement context and state handling; campaigns; callbacks; or version-drift troubleshooting."
triggers:
- "contact center sdk"
- "zoom contact center"
- "zcc"
- "engagement context"
- "engagement status"
- "campaign sdk"
- "scheduled callback"
- "getengagementcontext"
- "onengagementstatuschange"
- "zoom contact center app"
---
# /build-zoom-contact-center-app
Background reference for Zoom Contact Center integrations across app, web, and native mobile surfaces.
Implementation guidance for Zoom Contact Center across:
- Contact Center apps in the Zoom client (Zoom Apps SDK path)
- Web channel embeds (chat/video/campaign)
- Native mobile SDKs (Android/iOS)
Official docs:
- https://developers.zoom.us/docs/contact-center/
- https://developers.zoom.us/docs/contact-center/web/sdk-reference/
- https://marketplacefront.zoom.us/sdk/contact/android/index.html
- https://marketplacefront.zoom.us/sdk/contact/ios/index.html
## Routing Guardrail
- If the user is building an app inside the Zoom Contact Center desktop client, stay on the Zoom Apps SDK path and use this skill plus `zoom-apps-sdk`.
- If the user is embedding chat/video widgets on a website, route to [web/SKILL.md](web/SKILL.md).
- If the user is integrating native Android or iOS SDK binaries, route to [android/SKILL.md](android/SKILL.md) or [ios/SKILL.md](ios/SKILL.md).
- If the user needs Contact Center call-control or queue APIs, chain with [../rest-api/SKILL.md](../rest-api/SKILL.md).
## Quick Links
Start here:
1. [concepts/architecture-and-lifecycle.md](concepts/architecture-and-lifecycle.md)
2. [scenarios/high-level-scenarios.md](scenarios/high-level-scenarios.md)
3. [references/forum-top-questions.md](references/forum-top-questions.md)
4. [references/versioning-and-compatibility.md](references/versioning-and-compatibility.md)
5. [references/samples-validation.md](references/samples-validation.md)
6. [references/environment-variables.md](references/environment-variables.md)
7. [troubleshooting/common-drift-and-breaks.md](troubleshooting/common-drift-and-breaks.md)
8. [RUNBOOK.md](RUNBOOK.md)
Platform skills:
- [android/SKILL.md](android/SKILL.md)
- [ios/SKILL.md](ios/SKILL.md)
- [web/SKILL.md](web/SKILL.md)
## Documentation Structure
```
contact-center/
├── SKILL.md
├── RUNBOOK.md
├── concepts/
│ └── architecture-and-lifecycle.md
├── scenarios/
│ └── high-level-scenarios.md
├── references/
│ ├── versioning-and-compatibility.md
│ ├── samples-validation.md
│ └── environment-variables.md
├── troubleshooting/
│ └── common-drift-and-breaks.md
├── android/
│ ├── SKILL.md
│ ├── concepts/sdk-lifecycle.md
│ ├── examples/service-patterns.md
│ ├── references/android-reference-map.md
│ └── troubleshooting/common-issues.md
├── ios/
│ ├── SKILL.md
│ ├── concepts/sdk-lifecycle.md
│ ├── examples/service-patterns.md
│ ├── references/ios-reference-map.md
│ └── troubleshooting/common-issues.md
└── web/
├── SKILL.md
├── concepts/lifecycle-and-events.md
├── examples/app-context-and-state.md
├── references/web-reference-map.md
└── troubleshooting/common-issues.md
```
## Common Lifecycle Pattern
1. Initialize platform context early.
2. Build a channel item (`entryId` for chat/video/ZVA, `apiKey` for scheduled callback and campaign flows).
3. Get service/client instance.
4. Register listeners/delegates before user interaction.
5. Start flow (`fetchUI`, `startVideo`, or web SDK open/show path).
6. Handle engagement state changes (`start`, `hold`, `resume`, `end`) and context switching.
7. End flow and release resources (`endChat`/`endVideo`, `logout/logoff`, uninitialize/release).
## High-Level Scenarios
- Agent side-panel app that stores notes per `engagementId` and survives context switching.
- Browser chat/video campaigns launched from web tags.
- Native mobile customer app for chat/video/scheduled callback.
- Campaign-driven channel selection (chat, ZVA, video, scheduled callback).
- Rejoin flow for dropped video engagements on mobile.
- Smart Embed CRM softphone with postMessage event contracts.
See [scenarios/high-level-scenarios.md](scenarios/high-level-scenarios.md) for details.
## Chaining
- Auth and in-client app identity: [../zoom-apps-sdk/SKILL.md](../zoom-apps-sdk/SKILL.md) and [../oauth/SKILL.md](../oauth/SKILL.md)
- Contact Center REST workflows: [../rest-api/SKILL.md](../rest-api/SKILL.md)
- Cobrowse on web voice/chat channels: [../cobrowse-sdk/SKILL.md](../cobrowse-sdk/SKILL.md)
## Environment Variables
- See [references/environment-variables.md](references/environment-variables.md) for standardized `.env` keys and where to find each value.
@@ -0,0 +1,64 @@
# 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
- `entryId` for chat/video/ZVA entry points.
- `apiKey` for 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`/`apiKey` or 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/`
@@ -0,0 +1,53 @@
---
name: contact-center/android
description: "Zoom Contact Center SDK for Android. Use for native Android chat/video/ZVA/scheduled callback integrations, campaign mode, service lifecycle, and rejoin handling."
user-invocable: false
triggers:
- "contact center android"
- "zcc android"
- "zoomccinterface android"
- "zoomccchatservice"
- "zoomccvideoservice"
- "releasezoomccservice"
- "android rejoin"
---
# Zoom Contact Center SDK - Android
Official docs:
- https://developers.zoom.us/docs/contact-center/android/
- https://marketplacefront.zoom.us/sdk/contact/android/index.html
## Quick Links
1. [concepts/sdk-lifecycle.md](concepts/sdk-lifecycle.md)
2. [examples/service-patterns.md](examples/service-patterns.md)
3. [references/android-reference-map.md](references/android-reference-map.md)
4. [troubleshooting/common-issues.md](troubleshooting/common-issues.md)
## SDK Surface Summary
- SDK manager: `ZoomCCInterface`
- Channel services:
- `getZoomCCChatService()`
- `getZoomCCVideoService()`
- `getZoomCCZVAService()`
- `getZoomCCScheduledCallbackService()`
- Campaign support via web campaign service and campaign metadata.
## Hard Guardrails
- Initialize SDK in `Application.onCreate`.
- Use `ZoomCCItem` to define channel + identifiers.
- Use `entryId` for chat/video/ZVA.
- Use `apiKey` for scheduled callback and campaign mode.
- Release services on teardown.
## Common Chains
- Contact Center app and engagement context: [../../zoom-apps-sdk/SKILL.md](../../zoom-apps-sdk/SKILL.md)
- Contact Center API automation: [../../rest-api/SKILL.md](../../rest-api/SKILL.md)
## Operations
- [RUNBOOK.md](RUNBOOK.md) - 5-minute preflight and debugging checklist.
@@ -0,0 +1,42 @@
# 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:
- `sdkType`
- `entryId` or `apiKey`
- `serverType`
- campaign fields when needed
3. `service.init(item)`.
4. Add listener(s).
## Launch
1. Chat/ZVA:
- call `login()` then `fetchUI()`.
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.
@@ -0,0 +1,68 @@
# Android Service Patterns
## Chat Pattern
```kotlin
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
```kotlin
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
```kotlin
val service = ZoomCCInterface.getZoomCCScheduledCallbackService()
service.init(
ZoomCCItem(
apiKey = callbackApiKey,
sdkType = ZoomCCIInterfaceType.SCHEDULED_CALLBACK,
serverType = CCServerType.CCServerWWW
)
)
service.fetchUI()
```
## Cleanup Pattern
```kotlin
override fun onDestroy() {
ZoomCCInterface.releaseZoomCCService(chatEntryId)
ZoomCCInterface.releaseZoomCCService(videoEntryId)
ZoomCCInterface.releaseZoomCCService(callbackApiKey)
super.onDestroy()
}
```
@@ -0,0 +1,51 @@
# Android Reference Map
Primary reference:
- https://marketplacefront.zoom.us/sdk/contact/android/index.html
## Core Types
- `ZoomCCInterface`
- `ZoomCCItem`
- `ZoomCCContext`
- `ZoomCCService`
- `ZoomCCChatService`
- `ZoomCCVideoService`
- `ZoomCCScheduledCallbackService`
## Listener Types
- `ZoomCCServiceListener`
- `ZoomCCChatListener`
- `ZoomCCVideoListener`
## Enums
- `ZoomCCIInterfaceType`
- `ClientEvent`
- `IMStatus`
- `CCServerType`
- `VideoPreviewOption`
## 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.html` in each SDK version package.
- Keep runtime guards for enum/value additions and optional callbacks.
@@ -0,0 +1,44 @@
# 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:
- `entryId` for chat/video/ZVA.
- `apiKey` for 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.
@@ -0,0 +1,63 @@
# 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 `engagementId` as 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-context` header.
- Keep OAuth and app context decryption on backend where possible.
@@ -0,0 +1,64 @@
# 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
- `entryId` for chat/video/ZVA entry points.
- `apiKey` for 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`/`apiKey` or 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/`
@@ -0,0 +1,52 @@
---
name: contact-center/ios
description: "Zoom Contact Center SDK for iOS. Use for native iOS chat/video/ZVA/scheduled callback integrations, app lifecycle bridging, rejoin flow, and callback handling."
user-invocable: false
triggers:
- "contact center ios"
- "zcc ios"
- "zoomccinterface ios"
- "handleRejoinVideoOpenURL"
- "zoomccservicedelegate"
- "scheduled callback ios"
---
# Zoom Contact Center SDK - iOS
Official docs:
- https://developers.zoom.us/docs/contact-center/ios/
- https://marketplacefront.zoom.us/sdk/contact/ios/index.html
## Quick Links
1. [concepts/sdk-lifecycle.md](concepts/sdk-lifecycle.md)
2. [examples/service-patterns.md](examples/service-patterns.md)
3. [references/ios-reference-map.md](references/ios-reference-map.md)
4. [troubleshooting/common-issues.md](troubleshooting/common-issues.md)
## SDK Surface Summary
- Manager: `ZoomCCInterface.sharedInstance()`
- Context: `ZoomCCContext`
- Items: `ZoomCCItem`
- Services:
- `chatService`
- `zvaService`
- `videoService`
- `scheduledCallbackService`
## Hard Guardrails
- Set `ZoomCCContext` before channel operations.
- Forward app lifecycle calls (`appDidBecomeActive`, `appDidEnterBackgroud`, `appWillResignActive`, `appWillTerminate`).
- Use item-based initialization for channels.
- Keep rejoin URL handling connected to the video service path.
## Common Chains
- Contact Center apps in Zoom client: [../../zoom-apps-sdk/SKILL.md](../../zoom-apps-sdk/SKILL.md)
- OAuth and identity: [../../oauth/SKILL.md](../../oauth/SKILL.md)
## Operations
- [RUNBOOK.md](RUNBOOK.md) - 5-minute preflight and debugging checklist.
@@ -0,0 +1,46 @@
# 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` -> `appDidBecomeActive`
- `applicationWillResignActive` -> `appWillResignActive`
- `applicationDidEnterBackground` -> `appDidEnterBackgroud`
- `applicationWillTerminate` -> `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:
- `endChat`
- `endVideo`
- `endScheduledCallback`
- Use service `logout` / uninitialize patterns when needed by flow design.
@@ -0,0 +1,72 @@
# iOS Service Patterns
## Context Setup
```swift
let context = ZoomCCContext()
context.cacheFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
context.userName = userName
context.domainType = .US01
ZoomCCInterface.sharedInstance().context = context
```
## Chat Pattern
```swift
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
```swift
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
```swift
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
```swift
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return rootVC.handleRejoinVideoOpenURL(url)
}
```
@@ -0,0 +1,48 @@
# 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
- `ZoomCCInterface`
- `ZoomCCContext`
- `ZoomCCItem`
- `ZoomCCCampaignInfo`
## Service Protocols
- `ZoomCCService`
- `ZoomCCChatService`
- `ZoomCCVideoService`
- `ZoomCCScheduledCallbackService`
## Delegate Protocols
- `ZoomCCServiceDelegate`
- `ZoomCCChatServiceDelegate`
- `ZoomCCAppLifecyleDelegate`
## Key Methods
- Interface:
- `sharedInstance`
- `chatService`
- `zvaService`
- `videoService`
- `scheduledCallbackService`
- `getCampaigns`
- Service lifecycle:
- `initializeWithItem`
- `login`
- `logout`
- `fetchUI`
- Video:
- `handleRejoinVideoOpenURL:item:videoDelegate:complete:`
## Deprecation Note
- `onService:error:detail:` is deprecated.
- Use `onService:error:detail:description:`.
@@ -0,0 +1,42 @@
# iOS Common Issues
## Service Starts But View Never Appears
Cause:
- Missing `fetchUI` handling 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.
@@ -0,0 +1,25 @@
# 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.
@@ -0,0 +1,82 @@
---
title: "Forum-Derived Top Questions (Contact Center)"
---
# 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 `origin` usage and `postMessage` contract 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_changed` or 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_token` loops 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.
@@ -0,0 +1,48 @@
# 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 `init` with `ZoomCCItem`.
- Use `fetchUI` to present channel.
- `logoff` and `releaseZoomCCService` on cleanup.
3. iOS Native:
- Set `ZoomCCInterface.sharedInstance().context`.
- Initialize service with item.
- Use `fetchUI` to 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.
@@ -0,0 +1,41 @@
# 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.
@@ -0,0 +1,59 @@
# 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.
@@ -0,0 +1,62 @@
# 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 (`entryId` vs `apiKey` mismatch).
- 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.
@@ -0,0 +1,63 @@
# 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
- `entryId` for chat/video/ZVA entry points.
- `apiKey` for 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`/`apiKey` or 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/`
@@ -0,0 +1,54 @@
---
name: contact-center/web
description: "Zoom Contact Center SDK for Web. Use for web chat/video/campaign embeds, engagement event handling, app-context integrations, and Smart Embed postMessage workflows."
user-invocable: false
triggers:
- "contact center web"
- "zcc web sdk"
- "getengagementcontext web"
- "onengagementcontextchange"
- "contact center smart embed"
- "zcc-init-config-request"
---
# Zoom Contact Center SDK - Web
Official docs:
- https://developers.zoom.us/docs/contact-center/web/
- https://developers.zoom.us/docs/contact-center/web/sdk-reference/
## Quick Links
1. [concepts/lifecycle-and-events.md](concepts/lifecycle-and-events.md)
2. [examples/app-context-and-state.md](examples/app-context-and-state.md)
3. [references/web-reference-map.md](references/web-reference-map.md)
4. [troubleshooting/common-issues.md](troubleshooting/common-issues.md)
## Integration Modes
1. Contact Center App in Zoom client:
- Zoom Apps SDK engagement APIs/events.
2. External website embed:
- Campaign SDK/web scripts (`zoomCampaignSdk` pattern).
- Video client initialization pattern.
3. Smart Embed:
- iframe + `postMessage` event contract.
## Hard Guardrails
- For campaign SDK, gate calls behind `zoomCampaignSdk:ready`.
- Persist state by `engagementId`.
- Expect context switching and background app behavior.
- Validate CSP and allow-list settings before debugging logic.
## Chaining
- For in-client app APIs and auth flows: [../../zoom-apps-sdk/SKILL.md](../../zoom-apps-sdk/SKILL.md)
- For identity and OAuth: [../../oauth/SKILL.md](../../oauth/SKILL.md)
- For cobrowse workflow: [../../cobrowse-sdk/SKILL.md](../../cobrowse-sdk/SKILL.md)
## Operations
- [RUNBOOK.md](RUNBOOK.md) - 5-minute preflight and debugging checklist.
@@ -0,0 +1,45 @@
# 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:
- `onEngagementContextChange`
- `onEngagementStatusChange`
- 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:
- `open`
- `close`
- `show`
- `hide`
- `endChat`
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 `end` status as cleanup boundary.
@@ -0,0 +1,60 @@
# Web Example: Engagement-Aware State
```javascript
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
```javascript
window.addEventListener("zoomCampaignSdk:ready", () => {
if (!window.zoomCampaignSdk) return;
window.zoomCampaignSdk.show();
});
```
@@ -0,0 +1,54 @@
# 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)
- `getEngagementContext`
- `getEngagementStatus`
- `onEngagementContextChange`
- `onEngagementStatusChange`
- `onEngagementVariableValueChange`
## Campaign SDK Events
- `open`
- `close`
- `show`
- `hide`
- `engagement_started`
- `engagement_ended`
## Campaign SDK Methods
- `open()`
- `close()`
- `show()`
- `hide()`
- `endChat()`
- `waitForInit()`
- `waitForReady()`
- `updateUserContext()`
## Video Client Events
- `video-start`
- `video-end`
- `notification-join-call`
- `video-click-end`
- `video-force-end`
- `task-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
@@ -0,0 +1,42 @@
# Web Common Issues
## `zoomCampaignSdk` Is Undefined
Cause:
- Calls happen before readiness event.
Fix:
- Wait for `zoomCampaignSdk:ready` before 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-context` header 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.
@@ -0,0 +1,42 @@
---
name: debug-zoom-integration
description: Debug broken Zoom implementations quickly. Use when auth, webhooks, SDK joins, MCP transport, or real-time media workflows are failing and you need to isolate the layer before proposing a fix.
user-invocable: false
---
# Debug Zoom Integration
Use this skill when the user already built something and it is failing.
## Triage Order
1. Auth and app configuration
2. Request construction or event verification
3. SDK initialization or platform mismatch
4. Media/session behavior
5. MCP transport and capability assumptions
## Evidence To Request
- Exact error text
- Platform and SDK/runtime
- Relevant request or payload sample
- What worked versus what failed
- Whether the issue is reproducible or intermittent
## Reference Routing
- [oauth](../oauth/SKILL.md)
- [rest-api](../rest-api/SKILL.md)
- [webhooks](../webhooks/SKILL.md)
- [meeting-sdk](../meeting-sdk/SKILL.md)
- [video-sdk](../video-sdk/SKILL.md)
- [rtms](../rtms/SKILL.md)
- [zoom-mcp](../zoom-mcp/SKILL.md)
## Output
- Most likely failing layer
- Ranked hypotheses
- Short fix plan
- Verification steps
@@ -0,0 +1,39 @@
---
name: debug-zoom
description: Debug a broken Zoom integration by isolating the failure point and routing into the right Zoom references. Use when auth, API, webhook, SDK, or MCP behavior is failing and you need a ranked hypothesis list plus verification steps.
argument-hint: "<symptoms, error, or failing flow>"
---
# /debug-zoom
> If you see unfamiliar placeholders or need to check which tools are connected, see [CONNECTORS.md](../../CONNECTORS.md).
Debug Zoom auth, API, webhook, SDK, or MCP issues without wandering through the entire docs set.
## Usage
```text
/debug-zoom $ARGUMENTS
```
## Workflow
1. Identify the failing layer: auth, API request, webhook, SDK init, media/session behavior, or MCP transport.
2. Ask for the minimum missing evidence: exact error, platform, request/response, event payload, or code path.
3. Produce 2-4 plausible causes ranked by likelihood.
4. Route to the most relevant deep references in `skills/`.
5. Give a short verification plan so the user can confirm the fix.
## Output
- Most likely failure layer
- Ranked hypotheses
- Targeted fix steps
- Verification checklist
- Relevant skill links
## Related Skills
- [debug-zoom-integration](../debug-zoom-integration/SKILL.md)
- [setup-zoom-oauth](../setup-zoom-oauth/SKILL.md)
- [design-mcp-workflow](../design-mcp-workflow/SKILL.md)
@@ -0,0 +1,31 @@
---
name: design-mcp-workflow
description: Design a Zoom MCP workflow for Claude. Use when deciding whether Zoom MCP fits a task, when planning tool-based AI workflows, or when separating MCP responsibilities from REST API responsibilities.
user-invocable: false
---
# Design MCP Workflow
Use this skill when the user wants Claude or another MCP-capable client to interact with Zoom via tool calls instead of only deterministic API code.
## Covers
- MCP fit assessment
- REST API vs MCP boundaries
- Hybrid architectures
- Connector expectations
- Whiteboard-specific MCP routing
## Workflow
1. Decide whether the problem is agentic tooling, deterministic automation, or both.
2. Route MCP-only tasks to [zoom-mcp](../zoom-mcp/SKILL.md).
3. Route hybrid tasks to both [zoom-mcp](../zoom-mcp/SKILL.md) and [rest-api](../rest-api/SKILL.md).
4. If Whiteboard is central, route to [zoom-mcp/whiteboard](../zoom-mcp/whiteboard/SKILL.md).
5. Call out transport, auth, and client capability assumptions explicitly.
## Common Mistakes
- Using MCP for deterministic backend jobs that should stay in REST
- Treating MCP as a replacement for all API design
- Ignoring client transport support and auth requirements
@@ -0,0 +1,65 @@
# General Skill 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
- Use `general` as the routing hub for cross-product intent selection.
- Confirm each use-case links to the correct product skill chain.
- Use this runbook before troubleshooting multi-product integrations.
## 2) Confirm Required Credentials
- Validate OAuth model selection (User OAuth vs Server-to-Server OAuth) before implementation.
- Ensure required scopes are documented in each use-case.
- Keep credential storage server-side; only expose short-lived tokens to clients.
## 3) Confirm Lifecycle Order
1. Pick product path (`REST`, `Meeting SDK`, `Video SDK`, `Apps SDK`, `Phone`, `Contact Center`, etc.).
2. Map auth flow and required scopes.
3. Define event model (`webhooks` or `websockets`) and correlation IDs.
4. Validate deployment model and operational monitoring requirements.
## 4) Confirm Event/State Handling
- Keep use-case assumptions explicit when combining multiple products.
- Store cross-system identifiers (meeting/session/call/engagement IDs) for traceability.
- Document fallback behavior when API names/fields drift between versions.
## 5) Confirm Cleanup + Upgrade Posture
- Remove stale route links whenever skills are renamed or moved.
- Keep `.env` key references centralized in environment variable reference docs.
- Refresh compatibility notes after each major SDK/API update cycle.
## 6) Quick Probes
- Routing matrix still points to existing `SKILL.md` files.
- Use-cases include at least one concrete implementation chain.
- OAuth/scopes guidance matches current Marketplace app model.
## 7) Fast Decision Tree
- Unsure between Meeting SDK and Video SDK -> route by UX model (Zoom meeting UI vs fully custom session).
- Need lowest-latency events -> use websockets; otherwise webhooks are acceptable.
- Scope/auth failures in execution -> pause and re-authorize with correct app type and scopes.
## 8) Source Checkpoints
### Official docs
- https://developers.zoom.us/
- https://marketplace.zoom.us/
- https://devforum.zoom.us/
### Raw docs in repo
- `raw-docs/developers.zoom.us/docs/`
- `raw-docs/marketplacefront.zoom.us/sdk/`
@@ -0,0 +1,320 @@
---
name: zoom-general
description: Cross-product Zoom reference skill. Use after the workflow is clear when you need shared platform guidance, app-model comparisons, authentication context, scopes, marketplace considerations, or API-vs-MCP routing.
user-invocable: false
triggers:
- "zoom integration"
- "getting started"
- "which zoom sdk"
- "zoom platform"
- "choose zoom api"
- "zoom scopes"
- "marketplace"
- "cross-product"
- "apis vs mcp"
- "api vs mcp"
---
# Zoom General (Cross-Product Skills)
Background reference for cross-product Zoom questions. Prefer the workflow skills first, then use this file for shared platform guidance and routing detail.
## How `zoom-general` Routes a Complex Developer Query
Use `zoom-general` as the classifier and chaining layer:
1. detect product signals in the query
2. pick one primary skill
3. attach secondary skills for auth, events, or deployment edges
4. ask one short clarifier only when two routes match with similar confidence
Minimal implementation:
```ts
type SkillId =
| 'zoom-general'
| 'zoom-rest-api'
| 'zoom-webhooks'
| 'zoom-oauth'
| 'zoom-meeting-sdk-web-component-view'
| 'zoom-video-sdk'
| 'zoom-mcp';
const hasAny = (q: string, words: string[]) => words.some((w) => q.includes(w));
function detectSignals(rawQuery: string) {
const q = rawQuery.toLowerCase();
return {
meetingCustomUi: hasAny(q, ['zoom meeting', 'custom ui', 'component view', 'embed meeting']),
customVideo: hasAny(q, ['video sdk', 'custom video session', 'peer-video-state-change']),
restApi: hasAny(q, ['rest api', '/v2/', 'create meeting', 'list users', 's2s oauth']),
webhooks: hasAny(q, ['webhook', 'x-zm-signature', 'event subscription', 'crc']),
oauth: hasAny(q, ['oauth', 'pkce', 'token refresh', 'account_credentials']),
mcp: hasAny(q, ['zoom mcp', 'agentic retrieval', 'tools/list', 'semantic meeting search']),
};
}
function pickPrimarySkill(s: ReturnType<typeof detectSignals>): SkillId {
if (s.meetingCustomUi) return 'zoom-meeting-sdk-web-component-view';
if (s.mcp) return 'zoom-mcp';
if (s.restApi) return 'zoom-rest-api';
if (s.customVideo) return 'zoom-video-sdk';
return 'zoom-general';
}
function buildChain(primary: SkillId, s: ReturnType<typeof detectSignals>): SkillId[] {
const chain = [primary];
if (s.oauth && !chain.includes('zoom-oauth')) chain.push('zoom-oauth');
if (s.webhooks && !chain.includes('zoom-webhooks')) chain.push('zoom-webhooks');
return chain;
}
```
Example:
- `Create a meeting, configure webhooks, and handle OAuth token refresh` ->
`zoom-rest-api -> zoom-oauth -> zoom-webhooks`
- `Build a custom video UI for a Zoom meeting on web` ->
`zoom-meeting-sdk-web-component-view`
For the full TypeScript implementation and handoff contract, use
[references/routing-implementation.md](references/routing-implementation.md).
## Choose Your Path
| I want to... | Use this skill |
|--------------|----------------|
| Build a custom web UI around a real Zoom meeting | **[zoom-meeting-sdk-web-component-view](../meeting-sdk/web/component-view/SKILL.md)** |
| Build deterministic automation/configuration/reporting with explicit request control | **[zoom-rest-api](../rest-api/SKILL.md)** |
| Receive event notifications (HTTP push) | **[zoom-webhooks](../webhooks/SKILL.md)** |
| Receive event notifications (WebSocket, low-latency) | **[zoom-websockets](../websockets/SKILL.md)** |
| Embed Zoom meetings in my app | **[zoom-meeting-sdk](../meeting-sdk/SKILL.md)** |
| Build custom video experiences (Web, React Native, Flutter, Android, iOS, macOS, Unity, Linux) | **[zoom-video-sdk](../video-sdk/SKILL.md)** |
| Build an app that runs inside Zoom client | **[zoom-apps-sdk](../zoom-apps-sdk/SKILL.md)** |
| Transcribe uploaded or stored media with AI Services Scribe | **[scribe](../scribe/SKILL.md)** |
| Access live audio/video/transcripts from meetings | **[zoom-rtms](../rtms/SKILL.md)** |
| Enable collaborative browsing for support | **[zoom-cobrowse-sdk](../cobrowse-sdk/SKILL.md)** |
| Build Contact Center apps and channel integrations | **[contact-center](../contact-center/SKILL.md)** |
| Build Virtual Agent web/mobile chatbot experiences | **[virtual-agent](../virtual-agent/SKILL.md)** |
| Build Zoom Phone integrations (Smart Embed, Phone API, webhooks, URI flows) | **[phone](../phone/SKILL.md)** |
| Build Team Chat apps and integrations | **[zoom-team-chat](../team-chat/SKILL.md)** |
| Build server-side integrations with Rivet (auth + webhooks + APIs) | **[rivet-sdk](../rivet-sdk/SKILL.md)** |
| Run browser/device/network preflight diagnostics before join | **[probe-sdk](../probe-sdk/SKILL.md)** |
| Add pre-built UI components for Video SDK | **[zoom-ui-toolkit](../ui-toolkit/SKILL.md)** |
| Implement OAuth authentication (all grant types) | **[zoom-oauth](../oauth/SKILL.md)** |
| Build AI-driven tool workflows (AI Companion/agents) over Zoom data | **[zoom-mcp](../zoom-mcp/SKILL.md)** |
| Build AI-driven Whiteboard workflows over Zoom Whiteboard MCP | **[zoom-mcp/whiteboard](../zoom-mcp/whiteboard/SKILL.md)** |
| Build enterprise AI systems with stable API core + AI tool layer | **[zoom-rest-api](../rest-api/SKILL.md)** + **[zoom-mcp](../zoom-mcp/SKILL.md)** |
## Planning Checkpoint: Rivet SDK (Optional)
When a user starts planning a server-side integration that combines auth + webhooks + API calls, ask this first:
- `Rivet SDK is a Node.js framework that bundles Zoom auth handling, webhook receivers, and typed API wrappers.`
- `Do you want to use Rivet SDK for faster scaffolding, or do you prefer a direct OAuth + REST implementation without Rivet?`
Routing after answer:
- If user chooses Rivet: chain `rivet-sdk` + `oauth` + `rest-api`.
- If user declines Rivet: chain `oauth` + `rest-api` (+ `webhooks` or product skill as needed).
### SDK vs REST Routing Matrix (Hard Stop)
| User intent | Correct path | Do not route to |
|-------------|--------------|-----------------|
| Embed Zoom meeting in app UI | `zoom-meeting-sdk` | REST-only `join_url` flow |
| Build custom web UI for a real Zoom meeting | `zoom-meeting-sdk-web-component-view` | `zoom-video-sdk` |
| Build custom video UI/session app | `zoom-video-sdk` | Meeting SDK or REST meeting links |
| Get browser join links / manage meeting resources | `zoom-rest-api` | Meeting SDK join implementation |
Routing guardrails:
- If user asks for SDK embed/join behavior, stay in SDK path.
- If the prompt says **meeting** plus **custom UI/video/layout/embed**, prefer `zoom-meeting-sdk-web-component-view`.
- Only use `zoom-video-sdk` when the user is building a custom session product rather than a Zoom meeting.
- Only use REST path for resource management, reporting, or link distribution unless user explicitly requests a mixed architecture.
- For executable classification/chaining logic and error handling, see [references/routing-implementation.md](references/routing-implementation.md).
### API vs MCP Routing Matrix (Hard Stop)
| User intent | Correct path | Why |
|-------------|--------------|-----|
| Deterministic backend automation, account/user configuration, reporting, scheduled jobs | `zoom-rest-api` | Explicit request/response control and repeatable behavior |
| AI agent chooses tools dynamically, cross-platform AI tool interoperability | `zoom-mcp` | MCP is optimized for dynamic tool discovery and agentic workflows |
| Enterprise AI architecture (stable core + adaptive AI layer) | `zoom-rest-api + zoom-mcp` | APIs run core system actions; MCP exposes curated AI tools/context |
Routing guardrails:
- Do not replace deterministic backend APIs with MCP-only routing.
- Do not force raw REST-first routing when the task is AI-agent tool orchestration.
- Prefer hybrid routing when the user needs both stable automation and AI-driven interactions.
- MCP remote server works over Streamable HTTP/SSE; use this path when the target client/agent supports MCP transports (for example Claude or VS Code).
- Do not design per-tenant custom MCP endpoint provisioning; Zoom MCP endpoints are shared at instance/cluster level.
- Source: https://developers.zoom.us/docs/mcp/library/resources/apis-vs-mcp/
### Ambiguity Resolution (Ask Before Routing)
When a prompt matches both API and MCP paths with similar confidence, ask one short clarifier before execution:
- `Do you want deterministic REST API automation, AI-agent MCP tooling, or a hybrid of both?`
Then route as:
- REST answer → `zoom-rest-api`
- MCP answer → `zoom-mcp`
- Hybrid answer → `zoom-rest-api + zoom-mcp`
### MCP Availability and Topology Notes
- Zoom-hosted MCP access is evolving; docs indicate a model where Zoom exposes product-scoped MCP servers (for example Meetings, Team Chat, Whiteboard).
- Use `zoom-mcp` as the parent MCP entry point.
- Route Whiteboard-specific MCP requests to **[zoom-mcp/whiteboard](../zoom-mcp/whiteboard/SKILL.md)**.
- When a request is product-specific and MCP coverage exists, route to that MCP product surface first; otherwise use REST/SDK skills for deterministic implementation.
### Webhooks vs WebSockets
Both receive event notifications, but differ in approach:
| Aspect | webhooks | zoom-websockets |
|--------|---------------|-----------------|
| Connection | HTTP POST to your endpoint | Persistent WebSocket |
| Latency | Higher | Lower |
| Security | Requires public endpoint | No exposed endpoint |
| Setup | Simpler | More complex |
| Best for | Most use cases | Real-time, security-sensitive |
## Common Use Cases
| Use Case | Description | Skills Needed |
|----------|-------------|---------------|
| [Meeting + Webhooks + OAuth Refresh](references/meeting-webhooks-oauth-refresh-orchestration.md) | Create a meeting, process real-time updates, and refresh OAuth tokens safely in one design | [zoom-rest-api](../rest-api/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) + [zoom-webhooks](../webhooks/SKILL.md) |
| [Scribe Transcription Pipeline](use-cases/scribe-transcription-pipeline.md) | Transcribe uploaded files or S3 archives with AI Services Scribe using fast mode or batch jobs | [scribe](../scribe/SKILL.md) + optional [zoom-rest-api](../rest-api/SKILL.md) + optional [zoom-webhooks](../webhooks/SKILL.md) |
| [APIs vs MCP Routing](use-cases/apis-vs-mcp-routing.md) | Decide whether to route to deterministic Zoom APIs, AI-driven MCP, or a hybrid design | [zoom-rest-api](../rest-api/SKILL.md) and/or [zoom-mcp](../zoom-mcp/SKILL.md) |
| [Custom Meeting UI (Web)](use-cases/custom-meeting-ui-web.md) | Build a custom video UI for a real Zoom meeting in a web app using Meeting SDK Component View | [zoom-meeting-sdk-web-component-view](../meeting-sdk/web/component-view/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) |
| [Meeting Automation](use-cases/meeting-automation.md) | Schedule, update, delete meetings programmatically | [zoom-rest-api](../rest-api/SKILL.md) |
| [Meeting Bots](use-cases/meeting-bots.md) | Build bots that join meetings for AI/transcription/recording | [meeting-sdk/linux](../meeting-sdk/linux/SKILL.md) + [zoom-rest-api](../rest-api/SKILL.md) + optional [zoom-webhooks](../webhooks/SKILL.md) |
| [High-Volume Meeting Platform](use-cases/high-volume-meeting-platform.md) | Design distributed meeting creation and event processing with retries, queues, and reconciliation | [zoom-rest-api](../rest-api/SKILL.md) + [zoom-webhooks](../webhooks/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) |
| [Recording & Transcription](use-cases/recording-transcription.md) | Download recordings, get transcripts | [zoom-webhooks](../webhooks/SKILL.md) + [zoom-rest-api](../rest-api/SKILL.md) |
| [Recording Download Pipeline](use-cases/recording-download-pipeline.md) | Auto-download recordings to your own storage (S3, GCS, etc.) | [zoom-webhooks](../webhooks/SKILL.md) + [zoom-rest-api](../rest-api/SKILL.md) |
| [Real-Time Media Streams](use-cases/real-time-media-streams.md) | Access live audio, video, transcripts via WebSocket | [zoom-rtms](../rtms/SKILL.md) + [zoom-webhooks](../webhooks/SKILL.md) |
| [In-Meeting Apps](use-cases/in-meeting-apps.md) | Build apps that run inside Zoom meetings | [zoom-apps-sdk](../zoom-apps-sdk/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) |
| [React Native Meeting Embed](use-cases/react-native-meeting-embed.md) | Embed meetings into iOS/Android React Native apps | [zoom-meeting-sdk-react-native](../meeting-sdk/react-native/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) |
| [Native Meeting SDK Multi-Platform Delivery](use-cases/native-meeting-sdk-multi-platform.md) | Align Android, iOS, macOS, and Unreal Meeting SDK implementations under one auth/version strategy | [zoom-meeting-sdk](../meeting-sdk/SKILL.md) + platform skills |
| [Native Video SDK Multi-Platform Delivery](use-cases/native-video-sdk-multi-platform.md) | Align Android, iOS, macOS, and Unity Video SDK implementations under one auth/version strategy | [zoom-video-sdk](../video-sdk/SKILL.md) + platform skills |
| [Electron Meeting Embed](use-cases/electron-meeting-embed.md) | Embed meetings into desktop Electron apps | [zoom-meeting-sdk-electron](../meeting-sdk/electron/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) |
| [Flutter Video Sessions](use-cases/flutter-video-sessions.md) | Build custom mobile video sessions in Flutter | [zoom-video-sdk-flutter](../video-sdk/flutter/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) |
| [React Native Video Sessions](use-cases/react-native-video-sessions.md) | Build custom mobile video sessions in React Native | [zoom-video-sdk-react-native](../video-sdk/react-native/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) |
| [Immersive Experiences](use-cases/immersive-experiences.md) | Custom video layouts with Layers API | [zoom-apps-sdk](../zoom-apps-sdk/SKILL.md) |
| [Collaborative Apps](use-cases/collaborative-apps.md) | Real-time shared state in meetings | [zoom-apps-sdk](../zoom-apps-sdk/SKILL.md) |
| [Contact Center App Lifecycle and Context Switching](use-cases/contact-center-app-lifecycle-and-context-switching.md) | Build Contact Center apps that handle engagement events and multi-engagement state | [contact-center](../contact-center/SKILL.md) + [zoom-apps-sdk](../zoom-apps-sdk/SKILL.md) |
| [Virtual Agent Campaign Web and Mobile Wrapper](use-cases/virtual-agent-campaign-web-mobile-wrapper.md) | Deliver one campaign-driven bot flow across web and native mobile wrappers | [virtual-agent](../virtual-agent/SKILL.md) + [contact-center](../contact-center/SKILL.md) |
| [Virtual Agent Knowledge Base Sync Pipeline](use-cases/virtual-agent-knowledge-base-sync-pipeline.md) | Sync external knowledge content into Zoom Virtual Agent using web sync or custom API connectors | [virtual-agent](../virtual-agent/SKILL.md) + [zoom-rest-api](../rest-api/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) |
| [Zoom Phone Smart Embed CRM Integration](use-cases/zoom-phone-smart-embed-crm.md) | Build CRM dialer and call logging flows using Smart Embed plus Phone APIs | [phone](../phone/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) + [zoom-webhooks](../webhooks/SKILL.md) |
| [Rivet Event-Driven API Orchestrator](use-cases/rivet-event-driven-api-orchestrator.md) | Build a Node.js backend that combines webhooks and API actions through Rivet module clients | [rivet-sdk](../rivet-sdk/SKILL.md) + [zoom-oauth](../oauth/SKILL.md) + [zoom-rest-api](../rest-api/SKILL.md) |
| [Probe SDK Preflight Readiness Gate](use-cases/probe-sdk-preflight-readiness-gate.md) | Add browser/device/network diagnostics and readiness policy before Meeting SDK or Video SDK joins | [probe-sdk](../probe-sdk/SKILL.md) + [zoom-meeting-sdk](../meeting-sdk/SKILL.md) or [zoom-video-sdk](../video-sdk/SKILL.md) |
## Complete Use-Case Index
- [APIs vs MCP Routing](use-cases/apis-vs-mcp-routing.md): choose API-only, MCP-only, or hybrid routing using official Zoom criteria.
- [AI Companion Integration](use-cases/ai-companion-integration.md): connect Zoom AI Companion capabilities into your app workflow.
- [AI Integration](use-cases/ai-integration.md): add summarization, transcription, or assistant logic using Zoom data surfaces.
- [Backend Automation (S2S OAuth)](use-cases/backend-automation-s2s-oauth.md): run server-side jobs with account-level OAuth credentials.
- [Collaborative Apps](use-cases/collaborative-apps.md): build shared in-meeting app state and interactions.
- [Contact Center Integration](use-cases/contact-center-integration.md): connect Zoom Contact Center signals into external systems.
- [Contact Center App Lifecycle and Context Switching](use-cases/contact-center-app-lifecycle-and-context-switching.md): implement event-driven engagement state and safe context switching in Contact Center apps.
- [Virtual Agent Campaign Web and Mobile Wrapper](use-cases/virtual-agent-campaign-web-mobile-wrapper.md): deploy campaign-based Virtual Agent chat across website and Android/iOS WebView wrappers.
- [Virtual Agent Knowledge Base Sync Pipeline](use-cases/virtual-agent-knowledge-base-sync-pipeline.md): automate knowledge-base ingestion with web sync strategy or custom API connector.
- [Zoom Phone Smart Embed CRM Integration](use-cases/zoom-phone-smart-embed-crm.md): integrate Smart Embed events, Phone APIs, and CRM workflows with migration-safe data handling.
- [Rivet Event-Driven API Orchestrator](use-cases/rivet-event-driven-api-orchestrator.md): build a Node.js backend that combines webhook handling and API orchestration with Rivet.
- [Probe SDK Preflight Readiness Gate](use-cases/probe-sdk-preflight-readiness-gate.md): run browser/device/network diagnostics before launching meeting or video session workflows.
- [Custom Video](use-cases/custom-video.md): decide between Video SDK and related components for custom session UX.
- [Custom Meeting UI (Web)](use-cases/custom-meeting-ui-web.md): use Meeting SDK Component View for a custom UI around a real Zoom meeting.
- [Scribe Transcription Pipeline](use-cases/scribe-transcription-pipeline.md): use AI Services Scribe for on-demand file transcription and batch archive processing.
- [Video SDK Bring Your Own Storage](use-cases/video-sdk-bring-your-own-storage.md): configure Video SDK cloud recordings to write directly to your own S3 bucket.
- [Customer Support Cobrowsing](use-cases/customer-support-cobrowsing.md): implement customer-agent collaborative browsing support flows.
- [Embed Meetings](use-cases/embed-meetings.md): embed Zoom meeting experience into your app.
- [Form Completion Assistant](use-cases/form-completion-assistant.md): build guided flows for form filling and completion assistance.
- [HD Video Resolution](use-cases/hd-video-resolution.md): enable and troubleshoot high-definition video requirements.
- [High-Volume Meeting Platform](use-cases/high-volume-meeting-platform.md): build distributed meeting creation and event processing with concrete fallback patterns.
- [Immersive Experiences](use-cases/immersive-experiences.md): use Zoom Apps Layers APIs for custom in-meeting visuals.
- [In-Meeting Apps](use-cases/in-meeting-apps.md): build Zoom Apps that run directly inside meeting and webinar contexts.
- [Marketplace Publishing](use-cases/marketplace-publishing.md): prepare and ship a Zoom app through Marketplace review.
- [Meeting Automation](use-cases/meeting-automation.md): create, update, and manage meetings programmatically.
- [Meeting Bots](use-cases/meeting-bots.md): build bots for meeting join, capture, and real-time analysis.
- [Native Meeting SDK Multi-Platform Delivery](use-cases/native-meeting-sdk-multi-platform.md): standardize Android, iOS, macOS, and Unreal Meeting SDK delivery with shared auth and version controls.
- [Native Video SDK Multi-Platform Delivery](use-cases/native-video-sdk-multi-platform.md): standardize Android, iOS, macOS, and Unity Video SDK delivery with shared auth and version controls.
- [Meeting Details with Events](use-cases/meeting-details-with-events.md): combine REST retrieval with webhook event streams.
- [Minutes Calculation](use-cases/minutes-calculation.md): compute usage and minute metrics across meetings/sessions.
- [Prebuilt Video UI](use-cases/prebuilt-video-ui.md): use UI Toolkit for faster Video SDK-based UI delivery.
- [QSS Monitoring](use-cases/qss-monitoring.md): monitor Zoom quality statistics and performance indicators.
- [Raw Recording](use-cases/raw-recording.md): capture raw streams for custom recording and processing pipelines.
- [Electron Meeting Embed](use-cases/electron-meeting-embed.md): embed meetings in an Electron desktop application.
- [Flutter Video Sessions](use-cases/flutter-video-sessions.md): build Video SDK sessions in Flutter mobile apps.
- [React Native Meeting Embed](use-cases/react-native-meeting-embed.md): embed Meeting SDK into React Native apps.
- [React Native Video Sessions](use-cases/react-native-video-sessions.md): build custom video sessions in React Native.
- [Real-Time Media Streams](use-cases/real-time-media-streams.md): consume live media/transcript streams via RTMS.
- [Recording Download Pipeline](use-cases/recording-download-pipeline.md): automate recording retrieval and storage pipelines.
- [Recording & Transcription](use-cases/recording-transcription.md): manage post-meeting recordings and transcript workflows.
- [Retrieve Meeting and Subscribe Events](use-cases/retrieve-meeting-and-subscribe-events.md): join REST meeting fetch with event subscriptions.
- [SaaS App OAuth Integration](use-cases/saas-app-oauth-integration.md): implement user-level OAuth in multi-tenant SaaS apps.
- [SDK Size Optimization](use-cases/sdk-size-optimization.md): reduce bundle/runtime footprint for SDK-based apps.
- [SDK Wrappers and GUI](use-cases/sdk-wrappers-gui.md): evaluate wrapper patterns and GUI frameworks around SDKs.
- [Team Chat LLM Bot](use-cases/team-chat-llm-bot.md): build a Team Chat bot with LLM-powered responses.
- [Testing and Development](use-cases/testing-development.md): local testing patterns, mocks, and safe development loops.
- [Token and Scope Troubleshooting](use-cases/token-and-scope-troubleshooting.md): debug OAuth scope and token mismatch issues.
- [Transcription Bot (Linux)](use-cases/transcription-bot-linux.md): run Linux meeting bots for live transcription workloads.
- [Usage Reporting and Analytics](use-cases/usage-reporting-analytics.md): collect and analyze usage/reporting data.
- [User and Meeting Creation](use-cases/user-and-meeting-creation.md): provision users and schedule meetings in one flow.
- [Web SDK Embedding](use-cases/web-sdk-embedding.md): embed meeting experiences in browser-based web apps.
- [Server-to-Server OAuth with Webhooks](use-cases/server-to-server-oauth-with-webhooks.md): combine account OAuth with event-driven backend processing.
- [Meeting Links vs Embedding](use-cases/meeting-links-vs-embedding.md): choose between `join_url` distribution and SDK embedding.
- [Enterprise App Deployment](use-cases/enterprise-app-deployment.md): deploy, govern, and operate Zoom integrations at enterprise scale.
## Prerequisites
1. Zoom account (Pro, Business, or Enterprise)
2. App created in [Zoom App Marketplace](https://marketplace.zoom.us/)
3. OAuth credentials (Client ID and Secret)
## References
- [Known Limitations & Quirks](references/known-limitations.md)
## Quick Start
1. Go to [marketplace.zoom.us](https://marketplace.zoom.us/)
2. Click **Develop****Build App**
3. Select app type (see [references/app-types.md](references/app-types.md))
4. Configure OAuth and scopes
5. Copy credentials to your application
## Detailed References
- **[references/authentication.md](references/authentication.md)** - OAuth 2.0, S2S OAuth, JWT patterns
- **[references/app-types.md](references/app-types.md)** - Decision guide for app types
- **[references/scopes.md](references/scopes.md)** - OAuth scopes reference
- **[references/marketplace.md](references/marketplace.md)** - Marketplace portal navigation
- **[references/query-routing-playbook.md](references/query-routing-playbook.md)** - Route complex queries to the right specialized skills
- **[references/interview-answer-routing.md](references/interview-answer-routing.md)** - Short interview-ready answer pattern for zoom-general routing
- **[references/routing-implementation.md](references/routing-implementation.md)** - Concrete TypeScript query classification and skill handoff contract
- **[references/automatic-skill-chaining-rest-webhooks.md](references/automatic-skill-chaining-rest-webhooks.md)** - Executable process for REST + webhook chained workflows
- **[references/meeting-webhooks-oauth-refresh-orchestration.md](references/meeting-webhooks-oauth-refresh-orchestration.md)** - Concrete design for meeting creation + webhook updates + OAuth token refresh
- **[references/distributed-meeting-fallback-architecture.md](references/distributed-meeting-fallback-architecture.md)** - High-volume distributed architecture with retries, circuit breakers, and reconciliation fallbacks
- **[references/community-repos.md](references/community-repos.md)** - Curated official Zoom sample repositories by product
## SDK Maintenance
- **[references/sdk-upgrade-guide.md](references/sdk-upgrade-guide.md)** - Version policy, upgrade steps
- **[references/sdk-upgrade-workflow.md](references/sdk-upgrade-workflow.md)** - Changelog + RSS, version-by-version reusable upgrade workflow
- **[references/sdk-logs-troubleshooting.md](references/sdk-logs-troubleshooting.md)** - Collecting SDK logs
## Resources
- **Official docs**: https://developers.zoom.us/
- **Marketplace**: https://marketplace.zoom.us/
- **Developer forum**: https://devforum.zoom.us/
## Environment Variables
- See [references/environment-variables.md](references/environment-variables.md) for standardized `.env` keys and where to find each value.
## Operations
- [RUNBOOK.md](RUNBOOK.md) - 5-minute preflight and debugging checklist.
@@ -0,0 +1,106 @@
# 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
- Mail
- 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/
@@ -0,0 +1,82 @@
# 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 APIs
```
### Authorization URL
```
https://zoom.us/oauth/authorize?response_type=code&client_id={clientId}&redirect_uri={redirectUri}
```
### Token Exchange
```bash
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
```bash
curl -X POST "https://zoom.us/oauth/token?grant_type=account_credentials&account_id={accountId}" \
-H "Authorization: Basic {base64(clientId:clientSecret)}"
```
### Response
```json
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600
}
```
## SDK JWT Signatures
For Meeting SDK and Video SDK authentication. See:
- [Meeting SDK Authorization](../../meeting-sdk/references/authorization.md)
- [Video SDK Authorization](../../video-sdk/references/authorization.md)
### 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/
@@ -0,0 +1,562 @@
# Authorization Patterns
Permission validation middleware and role-based access control for Zoom API integrations.
> **Note**: These are **implementation patterns for YOUR application** when building Zoom integrations. These are not Zoom's internal authorization mechanisms - they are examples of how to structure authorization logic in your own backend.
## Overview
When chaining multiple Zoom API calls, each step may require different scopes and permissions. This document provides patterns for validating authorization at each step before proceeding.
## Authorization Flow
```
┌─────────────────────────────────────────────────────────────────────────┐
│ AUTHORIZATION VALIDATION FLOW │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. Check Token Validity │
│ └── Is token expired? → Refresh or re-authenticate │
│ └── Is token revoked? → Re-authenticate │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 2. Validate Required Scopes │
│ └── Does token have scopes for this operation? │
│ └── If missing → Return 403 with required scopes │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 3. Check Resource Permissions │
│ └── Does user have access to this resource? │
│ └── Is user admin/owner/member? │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 4. Execute Operation │
│ └── Call Zoom API │
│ └── Handle API-level authorization errors │
└─────────────────────────────────────────────────────────────────────────┘
```
## Scope Validation Middleware
### Express.js Middleware
```javascript
const axios = require('axios');
/**
* Middleware to validate OAuth token has required scopes
* @param {string[]} requiredScopes - Scopes required for this route
*/
function requireScopes(requiredScopes) {
return async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({
error: 'unauthorized',
message: 'No access token provided'
});
}
try {
// Get token info to check scopes
const tokenInfo = await getTokenInfo(token);
// Check if token has all required scopes
const tokenScopes = tokenInfo.scope.split(' ');
const missingScopes = requiredScopes.filter(
scope => !tokenScopes.includes(scope)
);
if (missingScopes.length > 0) {
return res.status(403).json({
error: 'insufficient_scope',
message: 'Token missing required scopes',
required_scopes: requiredScopes,
missing_scopes: missingScopes,
your_scopes: tokenScopes
});
}
// Attach token info to request for downstream use
req.zoomToken = tokenInfo;
req.zoomScopes = tokenScopes;
next();
} catch (error) {
if (error.response?.status === 401) {
return res.status(401).json({
error: 'invalid_token',
message: 'Token is invalid or expired'
});
}
next(error);
}
};
}
/**
* Get token information including scopes
*
* IMPORTANT: Scopes are returned during OAuth token exchange, not from API calls.
* You should store the scopes when you receive the access token.
*/
async function getTokenInfo(accessToken) {
// For Server-to-Server OAuth: Decode JWT to get scopes
const parts = accessToken.split('.');
if (parts.length === 3) {
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
return {
scope: payload.scope || '',
exp: payload.exp,
aud: payload.aud
};
}
// For User OAuth tokens: Scopes are NOT available from API responses.
// You must store scopes when you receive them during token exchange.
//
// During OAuth token exchange, the response includes:
// {
// "access_token": "...",
// "token_type": "bearer",
// "scope": "user:read meeting:write ...", <-- Store this!
// "expires_in": 3600
// }
//
// Store the scope in your database alongside the token.
throw new Error(
'User OAuth token scopes must be stored during token exchange. ' +
'Cannot retrieve scopes from an opaque access token.'
);
}
/**
* Example: Store scopes during OAuth token exchange
*/
async function handleOAuthCallback(code) {
const response = await axios.post('https://zoom.us/oauth/token', null, {
params: {
grant_type: 'authorization_code',
code: code,
redirect_uri: REDIRECT_URI
},
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
});
const { access_token, refresh_token, scope, expires_in } = response.data;
// IMPORTANT: Store the scope along with the token
await saveTokenToDatabase({
accessToken: access_token,
refreshToken: refresh_token,
scope: scope, // <-- Store this for later permission checks
expiresAt: Date.now() + (expires_in * 1000)
});
return { access_token, scope };
}
// Usage
const express = require('express');
const app = express();
// Route requiring meeting:read scope
app.get('/api/meetings/:id',
requireScopes(['meeting:read']),
async (req, res) => {
// Token already validated, proceed with API call
const meeting = await getMeeting(req.params.id, req.headers.authorization);
res.json(meeting);
}
);
// Route requiring multiple scopes
app.post('/api/users/:id/meetings',
requireScopes(['user:read', 'meeting:write']),
async (req, res) => {
const meeting = await createMeeting(req.params.id, req.body, req.headers.authorization);
res.json(meeting);
}
);
```
### Scope Requirements by Operation
| Operation | User Scope | Admin Scope (S2S) |
|-----------|------------|-------------------|
| Get own user info | `user:read` | `user:read:admin` |
| List all users | N/A | `user:read:admin` |
| Create user | N/A | `user:write:admin` |
| Get own meetings | `meeting:read` | `meeting:read:admin` |
| Get any user's meetings | N/A | `meeting:read:admin` |
| Create meeting for self | `meeting:write` | `meeting:write:admin` |
| Create meeting for others | N/A | `meeting:write:admin` |
| List own recordings | `recording:read` | `recording:read:admin` |
| List any user's recordings | N/A | `recording:read:admin` |
| Delete own recording | `recording:write` | `recording:write:admin` |
| Delete any recording | N/A | `recording:write:admin` |
| Access own phone | `phone:read` | `phone:read:admin` |
| Access any user's phone | N/A | `phone:read:admin` |
| Manage phone settings | `phone:write` | `phone:write:admin` |
> **Note**: "N/A" means this operation requires admin-level scopes and cannot be done with user-level OAuth.
## Role-Based Access Control
### Define Roles
```javascript
/**
* Role definitions with allowed scopes
*/
const ROLES = {
admin: {
scopes: [
'user:read:admin', 'user:write:admin',
'meeting:read:admin', 'meeting:write:admin',
'recording:read:admin', 'recording:write:admin',
'account:read:admin', 'account:write:admin'
],
description: 'Full administrative access'
},
manager: {
scopes: [
'user:read:admin',
'meeting:read:admin', 'meeting:write:admin',
'recording:read:admin'
],
description: 'Manage meetings and view users'
},
user: {
scopes: [
'user:read',
'meeting:read', 'meeting:write',
'recording:read'
],
description: 'Manage own meetings and recordings'
},
viewer: {
scopes: [
'meeting:read',
'recording:read'
],
description: 'View-only access'
}
};
/**
* Check if user role has required scope
*/
function roleHasScope(role, requiredScope) {
const roleConfig = ROLES[role];
if (!roleConfig) return false;
return roleConfig.scopes.some(scope => {
// Exact match
if (scope === requiredScope) return true;
// Admin scope covers non-admin version
// e.g., meeting:read:admin covers meeting:read
if (scope.endsWith(':admin')) {
const baseScope = scope.replace(':admin', '');
if (baseScope === requiredScope) return true;
}
return false;
});
}
/**
* Middleware to require a specific role
*/
function requireRole(allowedRoles) {
return (req, res, next) => {
const userRole = req.user?.role; // From your auth system
if (!userRole || !allowedRoles.includes(userRole)) {
return res.status(403).json({
error: 'forbidden',
message: 'Insufficient role permissions',
required_roles: allowedRoles,
your_role: userRole || 'none'
});
}
next();
};
}
// Usage
app.delete('/api/users/:id',
requireRole(['admin']),
requireScopes(['user:write:admin']),
async (req, res) => {
// Only admins can delete users
await deleteUser(req.params.id);
res.json({ success: true });
}
);
```
## Permission Checking Between Chained Operations
### Chain Validation Pattern
```javascript
/**
* Validate permissions for a multi-step operation
* before executing any steps
*/
async function validateChainPermissions(operations, tokenScopes) {
const allRequiredScopes = new Set();
for (const op of operations) {
for (const scope of op.requiredScopes) {
allRequiredScopes.add(scope);
}
}
const missingScopes = [...allRequiredScopes].filter(
scope => !tokenScopes.includes(scope)
);
if (missingScopes.length > 0) {
return {
valid: false,
missingScopes,
message: `Cannot complete operation chain. Missing scopes: ${missingScopes.join(', ')}`
};
}
return { valid: true };
}
/**
* Execute a chain of operations with permission validation
*/
async function executeAuthorizedChain(operations, accessToken) {
// Get token scopes
const tokenInfo = await getTokenInfo(accessToken);
const tokenScopes = tokenInfo.scope.split(' ');
// Validate all permissions upfront
const validation = await validateChainPermissions(operations, tokenScopes);
if (!validation.valid) {
throw new Error(validation.message);
}
// Execute operations in sequence
const results = [];
for (const op of operations) {
console.log(`Executing: ${op.name}`);
try {
const result = await op.execute(accessToken, results);
results.push({ name: op.name, success: true, data: result });
} catch (error) {
// Check if it's an authorization error
if (error.response?.status === 403) {
throw new Error(`Authorization failed at step "${op.name}": ${error.response.data.message}`);
}
throw error;
}
}
return results;
}
// Example: User + Meeting creation chain
const userMeetingChain = [
{
name: 'createUser',
requiredScopes: ['user:write:admin'],
execute: async (token, previousResults) => {
return await createUser({
email: 'new@example.com',
firstName: 'New',
lastName: 'User'
}, token);
}
},
{
name: 'createMeeting',
requiredScopes: ['meeting:write:admin'],
execute: async (token, previousResults) => {
const user = previousResults.find(r => r.name === 'createUser').data;
return await createMeeting(user.id, {
topic: 'Onboarding Meeting'
}, token);
}
}
];
// Usage
try {
const results = await executeAuthorizedChain(userMeetingChain, accessToken);
console.log('Chain completed:', results);
} catch (error) {
console.error('Chain failed:', error.message);
}
```
## Graceful Degradation
### Handle Partial Permissions
```javascript
/**
* Execute with graceful degradation when permissions are partial
*/
async function executeWithDegradation(operations, accessToken) {
const tokenInfo = await getTokenInfo(accessToken);
const tokenScopes = tokenInfo.scope.split(' ');
const results = [];
for (const op of operations) {
// Check if we have permission for this operation
const hasPermission = op.requiredScopes.every(
scope => tokenScopes.includes(scope)
);
if (!hasPermission) {
if (op.required) {
// Required operation - fail the chain
throw new Error(`Missing required scopes for ${op.name}: ${op.requiredScopes.join(', ')}`);
} else {
// Optional operation - skip with warning
console.warn(`Skipping ${op.name}: insufficient permissions`);
results.push({
name: op.name,
skipped: true,
reason: 'insufficient_permissions',
required_scopes: op.requiredScopes
});
continue;
}
}
// Execute operation
const result = await op.execute(accessToken, results);
results.push({ name: op.name, success: true, data: result });
}
return results;
}
// Example with optional operations
const meetingWithOptionalRecording = [
{
name: 'getMeeting',
required: true,
requiredScopes: ['meeting:read'],
execute: async (token) => getMeetingDetails(meetingId, token)
},
{
name: 'getRecordings',
required: false, // Optional - won't fail chain
requiredScopes: ['recording:read'],
execute: async (token, prev) => {
const meeting = prev.find(r => r.name === 'getMeeting').data;
return getRecordings(meeting.uuid, token);
}
}
];
```
## Authorization Decision Flowchart
```
┌──────────────────────────────────────────────────────────────────────────┐
│ AUTHORIZATION DECISION FLOW │
└──────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ Receive Request │
└────────┬────────┘
┌────────────────────────┐
│ Is token present? │
└───────────┬────────────┘
┌───────────┴───────────┐
│ NO │ YES
▼ ▼
┌───────────────┐ ┌────────────────────┐
│ Return 401 │ │ Is token valid? │
│ Unauthorized │ └─────────┬──────────┘
└───────────────┘ │
┌───────────┴───────────┐
│ NO │ YES
▼ ▼
┌───────────────┐ ┌────────────────────┐
│ Return 401 │ │ Has required │
│ Invalid Token │ │ scopes? │
└───────────────┘ └─────────┬──────────┘
┌───────────┴───────────┐
│ NO │ YES
▼ ▼
┌───────────────┐ ┌────────────────────┐
│ Return 403 │ │ Has resource │
│ Insufficient │ │ access? │
│ Scope │ └─────────┬──────────┘
└───────────────┘ │
┌───────────┴───────────┐
│ NO │ YES
▼ ▼
┌───────────────┐ ┌────────────────┐
│ Return 403 │ │ Execute │
│ Forbidden │ │ Operation │
└───────────────┘ └────────────────┘
```
## Common Authorization Errors
| Status | Error | Cause | Solution |
|--------|-------|-------|----------|
| 401 | `invalid_token` | Token expired or revoked | Refresh token or re-authenticate |
| 401 | `unauthorized` | No token provided | Include Authorization header |
| 403 | `insufficient_scope` | Token missing required scope | Request additional scopes |
| 403 | `forbidden` | User lacks resource access | Check user permissions |
| 403 | `access_denied` | Admin-only operation | Use admin account |
## Best Practices
1. **Validate upfront** - Check all permissions before starting a chain
2. **Fail fast** - Return clear error messages with required scopes
3. **Graceful degradation** - Skip optional steps rather than fail entirely
4. **Audit logging** - Log all authorization decisions
5. **Principle of least privilege** - Request only needed scopes
6. **Token caching** - Cache token info to avoid repeated validation calls
## Real-World Examples
See these use-cases for authorization patterns in action:
- **[User + Meeting Creation](../use-cases/user-and-meeting-creation.md)** - Multi-step provisioning with scope validation
- **[Meeting Details with Events](../use-cases/meeting-details-with-events.md)** - REST API + webhooks with permission checking
- **[Meeting Automation](../use-cases/meeting-automation.md)** - Meeting management with admin scope requirements
## Resources
- **OAuth Scopes Reference**: https://developers.zoom.us/docs/integrations/oauth-scopes/
- **API Error Codes**: https://developers.zoom.us/docs/api/rest/error-handling/
- **Authentication Guide**: [authentication.md](authentication.md)
- **Scopes Reference**: [scopes.md](scopes.md)
@@ -0,0 +1,176 @@
# 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
```ts
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
```text
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 notifications
```
## Minimal Runnable Example (Node.js)
```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 retry `4xx` business errors blindly.
- Webhook ingestion: always return `200` after durable enqueue or local persistence.
- Idempotency: dedupe by `event_id` or (`event`,`event_ts`,`meeting_uuid`) composite key.
- Reconciliation: periodic REST poll to repair missed webhook events.
## Environment Variables
- `ZOOM_ACCOUNT_ID`
- `ZOOM_CLIENT_ID`
- `ZOOM_CLIENT_SECRET`
- `ZOOM_HOST_USER_ID` (required for S2S meeting creation; do not rely on `me`)
- `ZOOM_WEBHOOK_SECRET`
- `PORT`
@@ -0,0 +1,158 @@
# 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](https://github.com/zoom/meetingsdk-web-sample) | 643 | Web SDK sample - Component View and Client View |
| [meetingsdk-web](https://github.com/zoom/meetingsdk-web) | 324 | NPM package for embedding meetings |
| [meetingsdk-react-sample](https://github.com/zoom/meetingsdk-react-sample) | 177 | React integration sample |
| [meetingsdk-auth-endpoint-sample](https://github.com/zoom/meetingsdk-auth-endpoint-sample) | 124 | Generate Meeting SDK JWT signatures |
| [meetingsdk-angular-sample](https://github.com/zoom/meetingsdk-angular-sample) | 60 | Angular integration sample |
| [meetingsdk-vuejs-sample](https://github.com/zoom/meetingsdk-vuejs-sample) | 42 | Vue.js integration sample |
| [meetingsdk-javascript-sample](https://github.com/zoom/meetingsdk-javascript-sample) | 41 | Vanilla JavaScript sample |
| [meetingsdk-headless-linux-sample](https://github.com/zoom/meetingsdk-headless-linux-sample) | 3 | Headless Linux bot with Docker |
---
## Video SDK
### Official Samples (by Zoom)
| Repository | Stars | Description |
|------------|-------|-------------|
| [videosdk-web-sample](https://github.com/zoom/videosdk-web-sample) | 137 | Web Video SDK sample |
| [videosdk-web](https://github.com/zoom/videosdk-web) | 56 | NPM package for custom video |
| [videosdk-auth-endpoint-sample](https://github.com/zoom/videosdk-auth-endpoint-sample) | 23 | Generate Video SDK JWT signatures |
| [videosdk-zoom-ui-toolkit-web](https://github.com/zoom/videosdk-zoom-ui-toolkit-web) | 17 | Prebuilt video chat UI |
| [videosdk-zoom-ui-toolkit-react-sample](https://github.com/zoom/videosdk-zoom-ui-toolkit-react-sample) | 17 | UI Toolkit in React |
| [videosdk-nextjs-quickstart](https://github.com/zoom/videosdk-nextjs-quickstart) | 16 | Next.js integration |
| [videosdk-zoom-ui-toolkit-javascript-sample](https://github.com/zoom/videosdk-zoom-ui-toolkit-javascript-sample) | 11 | UI Toolkit in vanilla JS |
| [VideoSDK-Web-Telehealth](https://github.com/zoom/VideoSDK-Web-Telehealth) | 11 | Telehealth starter kit |
| [videosdk-workshop](https://github.com/zoom/videosdk-workshop) | 9 | Workshop project |
| [videosdk-s3-cloud-recordings](https://github.com/zoom/videosdk-s3-cloud-recordings) | 8 | Auto-upload recordings to S3 |
| [videosdk-web-helloworld](https://github.com/zoom/videosdk-web-helloworld) | 4 | Minimal hello world |
| [videosdk-zoom-ui-toolkit-angular-sample](https://github.com/zoom/videosdk-zoom-ui-toolkit-angular-sample) | 4 | UI Toolkit in Angular |
| [videosdk-zoom-ui-toolkit-vuejs-sample](https://github.com/zoom/videosdk-zoom-ui-toolkit-vuejs-sample) | 3 | UI Toolkit in Vue.js |
| [videosdk-vue-nuxt-quickstart](https://github.com/zoom/videosdk-vue-nuxt-quickstart) | 1 | Vue/Nuxt quickstart |
| [videosdk-electron-sample](https://github.com/zoom/videosdk-electron-sample) | 1 | Electron sample |
| [videosdk-linux-raw-recording-sample](https://github.com/zoom/videosdk-linux-raw-recording-sample) | - | Linux headless raw data capture |
---
## REST API
### Official Samples (by Zoom)
| Repository | Stars | Description |
|------------|-------|-------------|
| [oauth-sample-app](https://github.com/zoom/oauth-sample-app) | 91 | Node.js OAuth sample |
| [server-to-server-oauth-starter-api](https://github.com/zoom/server-to-server-oauth-starter-api) | 54 | S2S OAuth starter API |
| [api](https://github.com/zoom/api) | 44 | API v2 documentation |
| [user-level-oauth-starter](https://github.com/zoom/user-level-oauth-starter) | 27 | User-level OAuth starter |
| [server-to-server-oauth-token](https://github.com/zoom/server-to-server-oauth-token) | 15 | S2S token generation utility |
| [rivet-javascript](https://github.com/zoom/rivet-javascript) | 13 | Rivet API library (auth + webhooks + API) |
| [websocket-js-sample](https://github.com/zoom/websocket-js-sample) | 5 | WebSocket connection demo |
| [websocket-redis-example](https://github.com/zoom/websocket-redis-example) | 4 | WebSocket with Redis |
| [server-to-server-python-sample](https://github.com/zoom/server-to-server-python-sample) | 4 | Python S2S OAuth sample |
| [task-manager-sample](https://github.com/zoom/task-manager-sample) | 3 | Unified build flow showcase |
| [rivet-javascript-sample](https://github.com/zoom/rivet-javascript-sample) | 3 | Rivet standup bot sample |
| [sample-registration-app](https://github.com/zoom/sample-registration-app) | 3 | Webinar registration with rate limits |
---
## Webhooks
### Official Samples (by Zoom)
| Repository | Stars | Description |
|------------|-------|-------------|
| [webhook-sample](https://github.com/zoom/webhook-sample) | 34 | Receive Zoom webhooks (Node.js) |
| [zoom-webhook-verification-headers](https://github.com/zoom/zoom-webhook-verification-headers) | - | Custom header auth + webhook validation |
| [webhook-to-postgres](https://github.com/zoom/webhook-to-postgres) | 5 | Store webhooks in PostgreSQL |
| [Go-Webhooks](https://github.com/zoom/Go-Webhooks) | - | Go/Fiber webhook listener |
---
## Zoom Apps SDK
### Official Samples (by Zoom)
| Repository | Stars | Description |
|------------|-------|-------------|
| [zoomapps-sample-js](https://github.com/zoom/zoomapps-sample-js) | 66 | Hello World Zoom App (vanilla JS) |
| [zoomapps-advancedsample-react](https://github.com/zoom/zoomapps-advancedsample-react) | 55 | Advanced React sample |
| [appssdk](https://github.com/zoom/appssdk) | 49 | Zoom Apps SDK NPM package |
| [zoomapps-texteditor-vuejs](https://github.com/zoom/zoomapps-texteditor-vuejs) | 16 | Collaborate Mode text editor |
| [zoomapps-customlayout-js](https://github.com/zoom/zoomapps-customlayout-js) | 16 | Immersive Mode / Layers API |
| [zoomapps-workshop-sample](https://github.com/zoom/zoomapps-workshop-sample) | 6 | Getting started workshop |
| [zoomapps-serverless-vuejs](https://github.com/zoom/zoomapps-serverless-vuejs) | 6 | Serverless on Firebase |
| [zoomapps-cameramode-vuejs](https://github.com/zoom/zoomapps-cameramode-vuejs) | 6 | Camera Mode + Immersive Mode |
| [arlo-meeting-assistant](https://github.com/zoom/arlo-meeting-assistant) | 2 | RTMS-powered meeting assistant |
| [meetingbot-recall-sample](https://github.com/zoom/meetingbot-recall-sample) | 2 | Meeting bot with Recall.ai + Claude |
---
## RTMS (Real-Time Media Streams)
### Official Samples (by Zoom)
| Repository | Stars | Description |
|------------|-------|-------------|
| [zoom-rtms](https://github.com/zoom/rtms) | 29 | Cross-platform RTMS wrapper (Node.js, Python, Go) |
| [rtms-samples](https://github.com/zoom/rtms-samples) | 22 | Official RTMS sample apps |
| [rtms-developer-preview-js](https://github.com/zoom/rtms-developer-preview-js) | 3 | Developer preview hello world |
| [rtms-sdk-cpp](https://github.com/zoom/rtms-sdk-cpp) | 2 | C++ RTMS SDK (librtmsdk) |
| [rtms-meeting-assistant-starter-kit](https://github.com/zoom/rtms-meeting-assistant-starter-kit) | 1 | Meeting assistant starter kit |
| [rtms-quickstart-js](https://github.com/zoom/rtms-quickstart-js) | 1 | Node.js quickstart |
| [zoom_rtms_langchain_sample](https://github.com/zoom/zoom_rtms_langchain_sample) | 1 | LangChain + transcripts for action items |
---
## Team Chat & Chatbots
### Official Samples (by Zoom)
| Repository | Stars | Description |
|------------|-------|-------------|
| [unsplash-chatbot](https://github.com/zoom/unsplash-chatbot) | 19 | Send Unsplash photos in Team Chat |
| [node.js-chatbot](https://github.com/zoom/node.js-chatbot) | 18 | Node.js chatbot library |
| [vote-chatbot](https://github.com/zoom/vote-chatbot) | 10 | Voting bot for Team Chat |
| [catbot](https://github.com/zoom/catbot) | 9 | Cat photo bot |
| [node.js-chatbot-cli](https://github.com/zoom/node.js-chatbot-cli) | 8 | Chatbot CLI tool |
| [zoom-chatbot-claude-sample](https://github.com/zoom/zoom-chatbot-claude-sample) | 6 | Anthropic Claude in Team Chat |
| [Zoom-Chat-Neural-Search-Assistant-Sample](https://github.com/zoom/Zoom-Chat-Neural-Search-Assistant-Sample) | 2 | Cerebras + Exa search bot |
| [zoom-team-chat-shortcut-sample](https://github.com/zoom/zoom-team-chat-shortcut-sample) | 1 | Recording management shortcut |
| [zoom-teams-chat-snowflake-sample](https://github.com/zoom/zoom-teams-chat-snowflake-sample) | 1 | Snowflake + Cortex integration |
| [zoom-erp-chatbot-sample](https://github.com/zoom/zoom-erp-chatbot-sample) | 1 | Oracle ERP integration |
| [chatbot-nodejs-quickstart](https://github.com/zoom/chatbot-nodejs-quickstart) | - | Node.js chatbot quickstart |
| [chatbot-python-sample](https://github.com/zoom/chatbot-python-sample) | - | Python chatbot with threading |
---
## Cobrowse SDK
### Official Samples (by Zoom)
| Repository | Stars | Description |
|------------|-------|-------------|
| [CobrowseSDK-Quickstart](https://github.com/zoom/CobrowseSDK-Quickstart) | 1 | Cobrowse SDK quickstart |
| [cobrowsesdk-auth-endpoint-sample](https://github.com/zoom/cobrowsesdk-auth-endpoint-sample) | 2 | JWT generation for Cobrowse |
---
## Tooling & Utilities
### Official Tools (by Zoom)
| Repository | Stars | Description |
|------------|-------|-------------|
| [probesdk-web](https://github.com/zoom/probesdk-web) | 3 | Test device/network/server connection |
---
@@ -0,0 +1,459 @@
# Distributed Meeting Creation and Event Processing with Fallbacks
Use this architecture for high-volume meeting creation with resilient event processing.
## Core Architectural Considerations
1. **Separation of planes**
- Command plane: REST meeting creation/update APIs.
- Event plane: webhook ingestion and async projection.
2. **Idempotency and dedupe**
- Require caller-provided idempotency key per create request.
- Dedupe webhook events by stable event key.
3. **Token isolation**
- Central token broker with distributed lock (Redis/Postgres advisory lock).
4. **Backpressure and queueing**
- Queue all webhook events and meeting commands.
- Use DLQ for poison messages.
5. **Fallback mechanisms**
- Retry with exponential backoff + jitter for retriable failures (`429/5xx/network`).
- Circuit breaker around Zoom API dependency.
- Reconciliation poller when webhook delivery is delayed/missed.
## Reference Topology
```text
API Gateway
-> Meeting Command Service
-> Idempotency Store (Redis/Postgres)
-> Token Broker
-> Zoom REST API
-> Outbox/Event Bus
Webhook Ingress
-> Signature Verify + URL Validation
-> Queue (Kafka/SQS/Rabbit)
-> Projection Workers
-> Meeting State Store
Recovery Services
-> Retry Worker
-> Reconciliation Poller (REST pull)
-> Dead Letter Reprocessor
```
## Command Plane Example (Meeting Creation Service)
```ts
type CreateMeetingInput = {
idempotencyKey: string;
hostUserId: string; // explicit user for S2S
topic: string;
startTime: string;
duration: number;
};
type QueuePublisher = { publish: (topic: string, payload: object) => Promise<void> };
type IdempotencyStore = {
get: (key: string) => Promise<object | null>;
put: (key: string, value: object, ttlSec: number) => Promise<void>;
};
export async function createMeetingCommand(
input: CreateMeetingInput,
deps: {
tokenBroker: { getToken: () => Promise<string> };
idempotency: IdempotencyStore;
queue: QueuePublisher;
breaker: CircuitBreaker;
},
) {
const cached = await deps.idempotency.get(input.idempotencyKey);
if (cached) return cached;
if (!deps.breaker.canCall()) {
// degraded mode: queue command for delayed processing
await deps.queue.publish('meeting.create.delayed', input);
return { accepted: true, mode: 'degraded_queued' };
}
const op = async () => {
const token = await deps.tokenBroker.getToken();
const res = await fetch(
`https://api.zoom.us/v2/users/${encodeURIComponent(input.hostUserId)}/meetings`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
topic: input.topic,
type: 2,
start_time: input.startTime,
duration: input.duration,
}),
},
);
if (!res.ok) {
const err = new Error(`zoom_create_failed:${res.status}`);
(err as any).status = res.status;
throw err;
}
return res.json();
};
try {
const created = await retry(
op,
{ retries: 4, baseMs: 300, maxMs: 5000 },
(e) => [429, 500, 502, 503, 504].includes((e as any).status),
);
deps.breaker.recordSuccess();
await deps.idempotency.put(input.idempotencyKey, created, 3600);
await deps.queue.publish('meeting.created', { meetingId: created.id, hostUserId: input.hostUserId });
return created;
} catch (e) {
deps.breaker.recordFailure();
throw e;
}
}
```
## Event Plane Example (Webhook Ingress + Queue + Projection)
```ts
import crypto from 'crypto';
export function verifyWebhook(rawBody: string, ts: string, sig: string, secret: string): boolean {
// reject stale requests to reduce replay risk
const nowSec = Math.floor(Date.now() / 1000);
const tsSec = Number(ts || 0);
if (!Number.isFinite(tsSec) || Math.abs(nowSec - tsSec) > 300) return false;
const msg = `v0:${ts}:${rawBody}`;
const expected = `v0=${crypto.createHmac('sha256', secret).update(msg).digest('hex')}`;
return sig === expected;
}
export async function ingestWebhook(req: any, res: any, queue: QueuePublisher, secret: string) {
if (req.body.event === 'endpoint.url_validation') {
const plainToken = req.body.payload?.plainToken;
const encryptedToken = crypto.createHmac('sha256', secret).update(plainToken).digest('hex');
return res.json({ plainToken, encryptedToken });
}
const ts = String(req.headers['x-zm-request-timestamp'] || '');
const sig = String(req.headers['x-zm-signature'] || '');
const raw = String(req.rawBody || '');
if (!verifyWebhook(raw, ts, sig, secret)) return res.status(401).send('invalid_signature');
try {
// durable write first, then ack
await queue.publish('zoom.webhook.raw', req.body);
return res.status(200).send('ok');
} catch {
// non-200 triggers Zoom retry for at-least-once delivery
return res.status(503).send('queue_unavailable');
}
}
export async function projectEvent(evt: any, stateStore: any, dedupe: IdempotencyStore) {
const dedupeKey = `${evt.event}:${evt.event_ts}:${evt.payload?.object?.uuid || evt.payload?.object?.id || 'unknown'}`;
const seen = await dedupe.get(dedupeKey);
if (seen) return;
const id = String(evt.payload?.object?.id || '');
const current = (await stateStore.get(id)) || { status: 'unknown', participants: 0, lastEventTs: 0 };
if (evt.event_ts < current.lastEventTs) {
await dedupe.put(dedupeKey, { stale: true }, 86400);
return;
} // stale event guard
if (evt.event === 'meeting.started') current.status = 'in_progress';
if (evt.event === 'meeting.ended') current.status = 'ended';
if (evt.event === 'meeting.participant_joined') current.participants += 1;
if (evt.event === 'meeting.participant_left') current.participants = Math.max(0, current.participants - 1);
current.lastEventTs = evt.event_ts;
await stateStore.put(id, current);
await dedupe.put(dedupeKey, { ok: true }, 86400);
}
```
### Express raw-body setup (required for signature verification)
```ts
app.use(express.json({
verify: (req: any, _res, buf) => {
req.rawBody = buf.toString('utf8');
},
}));
```
## Retry + Circuit Breaker Example (TypeScript)
```ts
type RetryOptions = {
retries: number;
baseMs: number;
maxMs: number;
};
function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
function backoff(attempt: number, baseMs: number, maxMs: number) {
const exp = Math.min(maxMs, baseMs * 2 ** attempt);
const jitter = Math.floor(Math.random() * Math.min(250, exp / 4));
return exp + jitter;
}
export async function retry<T>(fn: () => Promise<T>, opts: RetryOptions, isRetriable: (e: any) => boolean): Promise<T> {
let lastErr: any;
for (let i = 0; i <= opts.retries; i += 1) {
try {
return await fn();
} catch (e) {
lastErr = e;
if (i === opts.retries || !isRetriable(e)) break;
await sleep(backoff(i, opts.baseMs, opts.maxMs));
}
}
throw lastErr;
}
export class CircuitBreaker {
private failures = 0;
private openUntil = 0;
constructor(private threshold = 5, private coolDownMs = 15_000) {}
canCall() {
return Date.now() > this.openUntil;
}
recordSuccess() {
this.failures = 0;
}
recordFailure() {
this.failures += 1;
if (this.failures >= this.threshold) {
this.openUntil = Date.now() + this.coolDownMs;
}
}
}
```
## Reconciliation Poller Example (Fallback for Missed Events)
```ts
export async function reconcileMeetingState(
meetingId: string,
hostUserId: string,
deps: {
tokenBroker: { getToken: () => Promise<string> };
stateStore: { get: (id: string) => Promise<any>; put: (id: string, v: any) => Promise<void> };
},
) {
const token = await deps.tokenBroker.getToken();
const res = await fetch(`https://api.zoom.us/v2/meetings/${encodeURIComponent(meetingId)}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) return;
const apiState = await res.json();
const projected = (await deps.stateStore.get(meetingId)) || {};
const merged = {
...projected,
status: apiState.status || projected.status,
topic: apiState.topic || projected.topic,
hostId: hostUserId,
reconciledAt: Date.now(),
};
await deps.stateStore.put(meetingId, merged);
}
```
## Distributed Coordination and Load-Balancing Considerations
- Partition command/event streams by `meetingId` or `hostUserId` so all updates for one meeting land on the same consumer shard.
- Use a distributed lock for shared singleton jobs (token refresh rotation, reconciliation scheduler leader).
- Keep webhook ingress stateless so horizontal autoscaling is safe behind L4/L7 load balancers.
- Apply queue consumer concurrency limits to protect downstream Zoom API quotas.
### Redis-Style Lock Skeleton
```ts
export async function withLock(lock: { acquire: (k: string, ttlMs: number) => Promise<boolean>; release: (k: string) => Promise<void> }, key: string, fn: () => Promise<void>) {
const got = await lock.acquire(key, 10_000);
if (!got) return;
try {
await fn();
} finally {
await lock.release(key);
}
}
```
## Token Broker Example (Cached Refresh + Distributed Lock)
```ts
type CachedToken = { accessToken: string; expiresAtMs: number };
export class TokenBroker {
constructor(
private cache: { get: (k: string) => Promise<CachedToken | null>; put: (k: string, v: CachedToken, ttlSec: number) => Promise<void> },
private lock: { acquire: (k: string, ttlMs: number) => Promise<boolean>; release: (k: string) => Promise<void> },
private fetchToken: () => Promise<{ access_token: string; expires_in: number }>,
) {}
async getToken(): Promise<string> {
const cached = await this.cache.get('zoom:s2s-token');
const now = Date.now();
if (cached && cached.expiresAtMs - now > 60_000) {
return cached.accessToken;
}
const gotLock = await this.lock.acquire('zoom:s2s-token:refresh', 10_000);
if (!gotLock) {
await sleep(200);
const retryCached = await this.cache.get('zoom:s2s-token');
if (retryCached && retryCached.expiresAtMs - Date.now() > 30_000) {
return retryCached.accessToken;
}
throw new Error('token_refresh_lock_contention');
}
try {
const fresh = await this.fetchToken();
const value = {
accessToken: fresh.access_token,
expiresAtMs: Date.now() + fresh.expires_in * 1000,
};
await this.cache.put('zoom:s2s-token', value, Math.max(60, fresh.expires_in - 90));
return value.accessToken;
} finally {
await this.lock.release('zoom:s2s-token:refresh');
}
}
}
```
## High-Volume Create Worker (Concurrency + Rate Protection)
```ts
type CreateJob = CreateMeetingInput & { attempts: number };
class TokenBucket {
private tokens: number;
private lastRefill = Date.now();
constructor(private readonly capacity: number, private readonly refillPerSec: number) {
this.tokens = capacity;
}
async take() {
while (true) {
const now = Date.now();
const elapsedSec = (now - this.lastRefill) / 1000;
this.tokens = Math.min(this.capacity, this.tokens + elapsedSec * this.refillPerSec);
this.lastRefill = now;
if (this.tokens >= 1) {
this.tokens -= 1;
return;
}
await sleep(100);
}
}
}
export async function runCreateWorker(
queue: { receiveBatch: (n: number) => Promise<CreateJob[]>; ack: (job: CreateJob) => Promise<void>; retryLater: (job: CreateJob, delayMs: number) => Promise<void> },
deps: {
createMeeting: (job: CreateJob) => Promise<void>;
breaker: CircuitBreaker;
limiter: TokenBucket;
},
concurrency = 8,
) {
while (true) {
const jobs = await queue.receiveBatch(concurrency);
await Promise.all(jobs.map(async (job) => {
if (!deps.breaker.canCall()) {
await queue.retryLater(job, 30_000);
return;
}
try {
await deps.limiter.take();
await deps.createMeeting(job);
deps.breaker.recordSuccess();
await queue.ack(job);
} catch (e: any) {
deps.breaker.recordFailure();
const delay = backoff(job.attempts, 500, 60_000);
await queue.retryLater({ ...job, attempts: job.attempts + 1 }, delay);
}
}));
}
}
```
## Reconciliation Scheduler (Lag Detection + Leader Election)
```ts
export async function reconcileLaggingMeetings(
deps: {
lock: { acquire: (k: string, ttlMs: number) => Promise<boolean>; release: (k: string) => Promise<void> };
stateStore: { listLagging: (ageMs: number, limit: number) => Promise<Array<{ meetingId: string; hostUserId: string }>> };
reconcile: (meetingId: string, hostUserId: string) => Promise<void>;
},
) {
await withLock(deps.lock, 'zoom:reconcile:leader', async () => {
const lagging = await deps.stateStore.listLagging(5 * 60_000, 250);
for (const item of lagging) {
await deps.reconcile(item.meetingId, item.hostUserId);
}
});
}
```
## DLQ Replay Worker
```ts
export async function replayDlq(
dlq: { receiveBatch: (n: number) => Promise<any[]>; ack: (msg: any) => Promise<void>; moveBack: (topic: string, msg: any) => Promise<void> },
topic = 'meeting.create.delayed',
) {
const failed = await dlq.receiveBatch(100);
for (const msg of failed) {
await dlq.moveBack(topic, { ...msg, replayedAt: Date.now() });
await dlq.ack(msg);
}
}
```
## Distributed State Rules
- Meeting state is event-sourced or projection-based, not only request-response based.
- Persist `last_seen_event_ts` and status transitions to handle out-of-order events.
- Add monotonic transition guards (e.g., do not move `ended -> in_progress`).
## Fallback Matrix
| Failure | Primary response | Fallback |
|---|---|---|
| Token refresh failure | Retry token exchange | Fail fast + alert + pause new create requests |
| REST `429` / `5xx` | Retry w/ backoff | Queue command for delayed retry |
| Webhook verification failure | Reject `401` | Alert security pipeline |
| Webhook processor down | Buffer in queue | DLQ + replay job |
| Missing webhook event | Detect via reconciliation lag | REST poll and repair projection |
| Dependency outage | Open circuit breaker | Serve degraded status + queued commands |
@@ -0,0 +1,38 @@
# 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 |
| `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](../../zoom-apps-sdk/references/environment-variables.md)
- [../../cobrowse-sdk/references/environment-variables.md](../../cobrowse-sdk/references/environment-variables.md)
- [../../meeting-sdk/references/environment-variables.md](../../meeting-sdk/references/environment-variables.md)
- [../../oauth/references/environment-variables.md](../../oauth/references/environment-variables.md)
- [../../rest-api/references/environment-variables.md](../../rest-api/references/environment-variables.md)
- [../../rtms/references/environment-variables.md](../../rtms/references/environment-variables.md)
- [../../team-chat/references/environment-variables.md](../../team-chat/references/environment-variables.md)
- [../../ui-toolkit/references/environment-variables.md](../../ui-toolkit/references/environment-variables.md)
- [../../video-sdk/references/environment-variables.md](../../video-sdk/references/environment-variables.md)
- [../../webhooks/references/environment-variables.md](../../webhooks/references/environment-variables.md)
- [../../websockets/references/environment-variables.md](../../websockets/references/environment-variables.md)
- [../../contact-center/references/environment-variables.md](../../contact-center/references/environment-variables.md)
- [../../phone/references/environment-variables.md](../../phone/references/environment-variables.md)
- [../../probe-sdk/references/environment-variables.md](../../probe-sdk/references/environment-variables.md)
## Probe SDK note
- Probe SDK core diagnostics do not require Zoom OAuth/Marketplace credentials.
@@ -0,0 +1,20 @@
# 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-webhooks` or `zoom-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](query-routing-playbook.md)
@@ -0,0 +1,101 @@
# Known Limitations & Quirks
Common gotchas and limitations developers encounter.
## Recording Limitations
### Minimum Recording Duration
**Recordings shorter than 3-5 seconds will NOT be saved.**
This applies to:
- Cloud recordings
- Local recordings via SDK
If you need to capture very short sessions, ensure the recording runs for at least 5 seconds.
## API Limitations
### Rate Limits
See [Rate Limits](../../rest-api/references/rate-limits.md) for detailed information.
Key points:
- Create/update meeting endpoints are **Heavy** (stricter limits)
- Response headers show remaining quota
- Implement exponential backoff for 429 errors
### Error Code 0
**The enum value 0 often represents SUCCESS, not failure.**
Always check the SDK enum values:
```cpp
// Example: Meeting SDK
SDKERR_SUCCESS = 0 // This is success!
SDKERR_UNKNOWN = 1 // This is an error
```
Don't assume 0 = error in your error handling.
## Video SDK Web Limitations
### Video Rendering Performance
**Use ONE rendering control for all videos, not one per participant.**
Multiple rendering controls severely degrade performance. See [Video SDK Web](../../video-sdk/web/references/web.md#video-rendering-best-practices).
### SharedArrayBuffer
Some features require SharedArrayBuffer headers:
```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```
As of v1.11.2, this is elective for basic functionality.
## SDK Signature Limitations
### Minimum Token Validity
Zoom may require `exp - iat >= 2 hours`.
**Workaround:** Set `iat` in the past:
```javascript
const iat = Math.floor(Date.now() / 1000) - 7200; // 2 hours ago
const exp = Math.floor(Date.now() / 1000) + 10; // 10 seconds from now
```
This gives you a short-lived token while satisfying the validity requirement.
## SDK Download
### Marketplace Sign-in Required
Meeting SDK and Video SDK (except Web npm packages) must be downloaded from [Marketplace](https://marketplace.zoom.us/) after signing in.
They are not available on public package managers for native platforms.
## Platform-Specific
### iOS
- Requires camera/microphone entitlements
- Background audio requires special configuration
### Android
- Requires runtime permissions for camera/mic
- ProGuard rules may be needed
### Linux
- Headless operation requires X virtual framebuffer (Xvfb) for some features
- Limited UI customization compared to other platforms
## Resources
- **Developer forum**: https://devforum.zoom.us/ (search for known issues)
- **Support**: https://devsupport.zoom.us/
@@ -0,0 +1,67 @@
# Zoom App Marketplace
Navigate the Zoom Marketplace developer portal.
## Overview
The [Zoom App Marketplace](https://marketplace.zoom.us/) is where you create, configure, and publish Zoom apps.
## Getting Started
1. Go to [marketplace.zoom.us](https://marketplace.zoom.us/)
2. Sign in with your Zoom account
3. Click **Develop****Build App**
4. Choose app type
5. Configure app settings
## Portal Sections
### Develop
- **Build App** - Create new apps
- **Manage** - Edit existing apps
- **Logs** - View API and webhook logs
### App Configuration
| Section | Purpose |
|---------|---------|
| **App Credentials** | SDK Key/Secret, Client ID/Secret |
| **Scopes** | Configure OAuth permissions |
| **Feature** | Enable Meeting SDK, Video SDK, Webhooks |
| **Activation** | Make app installable |
## SDK Downloads
**Important:** Meeting SDK and Video SDK must be downloaded from Marketplace after signing in. They are not available on public package managers (except Web SDKs via npm).
1. Go to your app's **Download** section
2. Select platform (iOS, Android, Windows, macOS, Linux)
3. Download SDK package
## Credentials
### OAuth Apps
- **Client ID** - Public identifier
- **Client Secret** - Keep secret, server-side only
### SDK Apps
- **SDK Key** - Used in JWT payload
- **SDK Secret** - Used to sign JWT, keep secret
## Publishing
To publish to Marketplace:
1. Complete app configuration
2. Submit for review
3. Address feedback
4. Get approved
5. Go live
## Resources
- **Marketplace**: https://marketplace.zoom.us/
- **Developer docs**: https://developers.zoom.us/
@@ -0,0 +1,173 @@
# Meeting + Webhooks + OAuth Refresh Orchestration
This guide implements one solution that handles all three simultaneously:
1. create meeting,
2. process webhook updates,
3. refresh OAuth tokens safely.
## Direct Answer
Use this skill chain:
1. `zoom-general` to classify the request
2. `zoom-oauth` for token brokerage and refresh control
3. `zoom-rest-api` to create the meeting
4. `zoom-webhooks` to receive real-time updates
Minimal flow:
```text
client request
-> TokenBroker.getToken()
-> POST /v2/users/{userId}/meetings
-> persist meeting + idempotency key
-> Zoom sends webhooks to your ingress
-> verify signature
-> enqueue event
-> projection worker updates meeting state
```
Webhook subscription note:
- the receiver implementation lives in your app code
- the actual Zoom event subscription is configured at the Marketplace app level
- do not model webhook subscription enablement as a per-request runtime API step unless Zoom exposes a product-specific admin API for that exact surface
## Skill Chain
1. `zoom-general`
2. `zoom-oauth`
3. `zoom-rest-api`
4. `zoom-webhooks`
## Component Design
- `TokenBroker`: central access token cache + refresh lock.
- `MeetingService`: REST calls using broker.
- `WebhookIngress`: signature validation + URL validation + event enqueue.
- `ProjectionWorker`: applies events to meeting state.
## Token Broker with Refresh Lock (TypeScript)
```ts
type TokenState = { accessToken: string; expiresAt: number; refreshing?: Promise<string> };
export class TokenBroker {
private state: TokenState = { accessToken: '', expiresAt: 0 };
constructor(
private accountId: string,
private clientId: string,
private clientSecret: string,
) {}
async getToken(): Promise<string> {
const now = Date.now();
if (this.state.accessToken && now < this.state.expiresAt - 60_000) {
return this.state.accessToken;
}
if (!this.state.refreshing) {
this.state.refreshing = this.refresh();
this.state.refreshing.finally(() => { this.state.refreshing = undefined; });
}
return this.state.refreshing;
}
invalidate() {
this.state.accessToken = '';
this.state.expiresAt = 0;
}
async forceRefresh(): Promise<string> {
this.invalidate();
return this.getToken();
}
private async refresh(): Promise<string> {
const q = new URLSearchParams({ grant_type: 'account_credentials', account_id: this.accountId });
const basic = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
const res = await fetch(`https://zoom.us/oauth/token?${q.toString()}`, {
method: 'POST',
headers: { Authorization: `Basic ${basic}` },
});
if (!res.ok) throw new Error(`token_refresh_failed:${res.status}`);
const data = await res.json() as { access_token: string; expires_in: number };
this.state.accessToken = data.access_token;
this.state.expiresAt = Date.now() + data.expires_in * 1000;
return this.state.accessToken;
}
}
```
## Meeting Service with 401 Retry-once
```ts
export async function createMeeting(tokenBroker: TokenBroker, userId: string, payload: object) {
async function call(): Promise<Response> {
const token = await tokenBroker.getToken();
return fetch(`https://api.zoom.us/v2/users/${encodeURIComponent(userId)}/meetings`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
}
let res = await call();
if (res.status === 401) {
await tokenBroker.forceRefresh();
res = await call(); // retry once with fresh token
}
if (!res.ok) throw new Error(`create_meeting_failed:${res.status}`);
return res.json();
}
```
## Webhook Ingress Skeleton
```ts
import crypto from 'crypto';
import type { Request, Response } from 'express';
export function verifyZoomSignature(req: Request, secret: string): boolean {
const ts = String(req.headers['x-zm-request-timestamp'] || '');
const sig = String(req.headers['x-zm-signature'] || '');
const rawBody = (req as any).rawBody || JSON.stringify(req.body);
const msg = `v0:${ts}:${rawBody}`;
const expected = `v0=${crypto.createHmac('sha256', secret).update(msg).digest('hex')}`;
return sig === expected;
}
export async function handleWebhook(req: Request, res: Response, secret: string, enqueue: (e: any) => Promise<void>) {
if (req.body?.event === 'endpoint.url_validation') {
const plainToken = req.body.payload?.plainToken;
const encryptedToken = crypto.createHmac('sha256', secret).update(plainToken).digest('hex');
return res.json({ plainToken, encryptedToken });
}
if (!verifyZoomSignature(req, secret)) {
return res.status(401).send('invalid_signature');
}
await enqueue(req.body); // durable queue write
return res.status(200).send('ok');
}
```
## Event Processing Rules
- Apply idempotency key to avoid duplicate state updates.
- Accept out-of-order events; keep `last_event_ts` and reject stale writes when necessary.
- Add reconciliation worker that polls REST meeting status if webhook lag or failures are detected.
## Runtime Setup Notes
- For Server-to-Server OAuth meeting creation, pass an explicit host `userId`/email instead of relying on `me`.
- In Express, capture raw request body in `express.json({ verify })` and use it for signature verification.
@@ -0,0 +1,87 @@
# Query Routing Playbook (zoom-general)
Use `zoom-general` as the routing/orchestration layer.
Do not implement product-specific logic in `zoom-general` if a specialized skill exists.
## Goal
Convert a complex developer query into:
- `selected_skills`
- `execution_order`
- `assumptions`
- `next_actions`
## Routing rules
| Query signal | Route to skill | Why |
|---|---|---|
| OAuth, scopes, S2S, token strategy | `zoom-oauth` | Authentication and authorization design |
| Meetings/users/recordings/reports API operations | `zoom-rest-api` | Server-side Zoom resource management |
| Embed full Zoom meetings/webinars | `zoom-meeting-sdk` | Meeting runtime integration |
| Build custom video session experience | `zoom-video-sdk` | Custom media UX runtime |
| Receive event callbacks via HTTP | `zoom-webhooks` | Event lifecycle notifications |
| Need lower-latency event stream | `zoom-websockets` | Persistent real-time event transport |
| Live audio/video/transcript stream ingestion | `zoom-rtms` | Real-time media and transcript pipeline |
| App runs inside Zoom client | `zoom-apps-sdk` | In-client app model and APIs |
## Sequencing
1. Start with `zoom-general` (triage and architecture).
2. Add `zoom-oauth` if any protected resource access is required.
3. Select one primary runtime/API skill (`zoom-meeting-sdk`, `zoom-video-sdk`, or `zoom-rest-api`).
4. Add event/media skills (`zoom-webhooks`, `zoom-websockets`, `zoom-rtms`) based on requirements.
5. Keep the chain minimal; do not add extra skills without explicit need.
## Handoff contract
```json
{
"selected_skills": [
"zoom-general",
"zoom-oauth",
"zoom-meeting-sdk",
"zoom-webhooks"
],
"execution_order": [
"zoom-general",
"zoom-oauth",
"zoom-meeting-sdk",
"zoom-webhooks"
],
"assumptions": [
"embedded meeting experience required",
"server-side event endpoint available"
],
"next_actions": [
"confirm OAuth scopes",
"implement auth/token flow",
"implement runtime integration",
"implement event consumer and verification"
]
}
```
## Ambiguity handling
If confidence is low, ask one focused question before final routing:
- “Do you need embedded Zoom meetings, or a fully custom video session UI?”
- “Is webhook latency acceptable, or do you require persistent low-latency events?”
## Example route
Query: “Build a Linux bot that joins meetings, auto-creates meetings, streams transcript, and tracks lifecycle events.”
Recommended chain:
- `zoom-general`
- `zoom-oauth`
- `zoom-rest-api`
- `zoom-meeting-sdk`
- `zoom-rtms`
- `zoom-webhooks`
Why:
- `zoom-rest-api` for meeting provisioning
- `zoom-meeting-sdk` for runtime join/control
- `zoom-rtms` for live transcript/media stream
- `zoom-webhooks` for lifecycle notifications
@@ -0,0 +1,247 @@
# Routing Implementation (zoom-general)
This reference provides a concrete implementation model for routing a complex developer query from `zoom-general` to specialized skills.
## Runtime Assumptions
- Runtime: Node.js 18+.
- Language: TypeScript 5+.
- Input: free-form developer prompt.
- Output: deterministic handoff contract with primary skill, chained skills, rationale, and follow-up questions (if required).
## TypeScript Router Example
```ts
export type SkillId =
| 'zoom-general'
| 'zoom-rest-api'
| 'zoom-mcp'
| 'zoom-mcp/whiteboard'
| 'zoom-webhooks'
| 'zoom-websockets'
| 'zoom-meeting-sdk'
| 'zoom-meeting-sdk-web'
| 'zoom-meeting-sdk-web-component-view'
| 'zoom-video-sdk'
| 'zoom-video-sdk-web'
| 'zoom-apps-sdk'
| 'zoom-rtms'
| 'zoom-team-chat'
| 'contact-center'
| 'virtual-agent'
| 'phone'
| 'rivet-sdk'
| 'probe-sdk'
| 'zoom-ui-toolkit'
| 'zoom-cobrowse-sdk'
| 'zoom-oauth';
export interface RouteDecision {
primarySkill: SkillId;
chainedSkills: SkillId[];
confidence: number;
rationale: string[];
needsClarification: string[];
warnings: string[];
}
interface Signals {
meetingEmbed: boolean;
meetingCustomUi: boolean;
customVideo: boolean;
restApi: boolean;
mcp: boolean;
whiteboardMcp: boolean;
webhooks: boolean;
websockets: boolean;
zoomApps: boolean;
oauth: boolean;
rtms: boolean;
teamChat: boolean;
contactCenter: boolean;
virtualAgent: boolean;
phone: boolean;
rivet: boolean;
preflight: boolean;
uiToolkit: boolean;
cobrowse: boolean;
}
const hasAny = (q: string, words: string[]): boolean => words.some((w) => q.includes(w));
export function detectSignals(rawQuery: string): Signals {
const q = rawQuery.toLowerCase();
return {
meetingEmbed: hasAny(q, ['meeting sdk', 'embed meeting', 'join meeting ui', 'client view', 'component view']),
meetingCustomUi: hasAny(q, [
'custom meeting ui',
'custom zoom meeting ui',
'custom meeting video ui',
'custom video ui for meeting',
'zoommtgembedded',
'zoomapproot',
'embeddable meeting ui',
'component view',
]),
customVideo: hasAny(q, ['video sdk', 'custom video', 'attachvideo', 'peer-video-state-change']),
restApi: hasAny(q, ['rest api', 'api create meeting', 'api list meetings', '/v2/', 'list users', 's2s oauth', 'meeting endpoint']),
mcp: hasAny(q, ['zoom mcp', 'mcp server', 'agentic retrieval', 'tools/list', 'tools/call', 'semantic meeting search']),
whiteboardMcp: hasAny(q, ['whiteboard mcp', 'zoom whiteboard mcp', 'list whiteboards', 'get a whiteboard', 'wb/db', 'whiteboard_id']),
webhooks: hasAny(q, ['webhook', 'x-zm-signature', 'event subscription', 'crc']),
websockets: hasAny(q, ['websocket', 'real-time events', 'persistent connection']),
zoomApps: hasAny(q, ['zoom apps sdk', 'in-client app', 'layers api', 'collaborate mode']),
oauth: hasAny(q, ['oauth', 'pkce', 'authorization code', 'account_credentials', 'token refresh']),
rtms: hasAny(q, ['rtms', 'real-time media streams', 'live transcript stream', 'audio stream']),
teamChat: hasAny(q, ['team chat', 'chatbot', 'chat card', 'chat message']),
contactCenter: hasAny(q, ['contact center', 'engagement context', 'contact center smart embed', 'zcc']),
virtualAgent: hasAny(q, ['virtual agent', 'zva', 'knowledge base sync', 'virtual assistant sdk']),
phone: hasAny(q, ['zoom phone', 'phone smart embed', 'phone api', 'click to dial']),
rivet: hasAny(q, ['rivet', 'zoom rivet']),
preflight: hasAny(q, ['probe sdk', 'preflight', 'diagnostics', 'network readiness']),
uiToolkit: hasAny(q, ['ui toolkit', 'prebuilt video ui']),
cobrowse: hasAny(q, ['cobrowse', 'co-browse', 'shared browsing']),
};
}
function pickPrimarySkill(s: Signals): SkillId {
// Hard guardrails: SDK embed/custom-video requests should not fall back to REST.
if (s.meetingCustomUi) return 'zoom-meeting-sdk-web-component-view';
if (s.meetingEmbed && !s.customVideo) return 'zoom-meeting-sdk-web';
if (s.meetingEmbed) return 'zoom-meeting-sdk';
if (s.customVideo && !s.meetingEmbed) return 'zoom-video-sdk-web';
if (s.customVideo) return 'zoom-video-sdk';
if (s.virtualAgent) return 'virtual-agent';
if (s.contactCenter) return 'contact-center';
if (s.zoomApps) return 'zoom-apps-sdk';
if (s.rtms) return 'zoom-rtms';
if (s.teamChat) return 'zoom-team-chat';
if (s.phone) return 'phone';
if (s.cobrowse) return 'zoom-cobrowse-sdk';
if (s.uiToolkit) return 'zoom-ui-toolkit';
if (s.preflight) return 'probe-sdk';
if (s.websockets) return 'zoom-websockets';
if (s.webhooks) return 'zoom-webhooks';
if (s.whiteboardMcp) return 'zoom-mcp/whiteboard';
if (s.mcp) return 'zoom-mcp';
if (s.restApi) return 'zoom-rest-api';
if (s.oauth) return 'zoom-oauth';
return 'zoom-general';
}
function buildChain(primary: SkillId, s: Signals): SkillId[] {
const chain = new Set<SkillId>();
if (primary === 'zoom-meeting-sdk-web-component-view') chain.add('zoom-meeting-sdk-web');
// Auth chaining.
if (s.oauth || s.restApi || s.mcp || s.webhooks || s.websockets || s.phone || s.teamChat || s.virtualAgent) {
chain.add('zoom-oauth');
}
// Optional server framework.
if (s.rivet) chain.add('rivet-sdk');
// Cross-surface chaining.
if (primary === 'contact-center' && s.virtualAgent) chain.add('virtual-agent');
if (primary === 'virtual-agent' && s.contactCenter) chain.add('contact-center');
// Event channels often pair with REST resource management.
if (s.webhooks || s.websockets) chain.add('zoom-rest-api');
if (s.mcp && s.restApi) {
chain.add('zoom-rest-api');
chain.add('zoom-mcp');
}
// Avoid redundant primary in chain.
chain.delete(primary);
return [...chain];
}
function validateDecision(primary: SkillId, s: Signals): string[] {
const warnings: string[] = [];
if (s.meetingEmbed && !['zoom-meeting-sdk', 'zoom-meeting-sdk-web', 'zoom-meeting-sdk-web-component-view'].includes(primary)) {
warnings.push('meeting embed intent detected but primary skill is not zoom-meeting-sdk');
}
if (s.meetingCustomUi && primary !== 'zoom-meeting-sdk-web-component-view') {
warnings.push('custom meeting UI intent detected but primary skill is not zoom-meeting-sdk-web-component-view');
}
if (s.customVideo && !['zoom-video-sdk', 'zoom-video-sdk-web'].includes(primary)) {
warnings.push('custom video intent detected but primary skill is not zoom-video-sdk');
}
if (s.meetingCustomUi && s.customVideo) {
warnings.push('meeting UI intent and custom video intent both detected; prefer Meeting SDK Component View unless the user explicitly wants a non-meeting session');
}
if (s.restApi && (s.meetingEmbed || s.customVideo)) {
warnings.push('mixed SDK + REST intent; keep SDK as primary and use REST only for resource workflows');
}
return warnings;
}
function confidenceFromSignals(s: Signals): number {
const hits = Object.values(s).filter(Boolean).length;
if (hits >= 4) return 0.9;
if (hits >= 2) return 0.78;
if (hits === 1) return 0.65;
return 0.5;
}
export function routeComplexQuery(query: string): RouteDecision {
const signals = detectSignals(query);
const primarySkill = pickPrimarySkill(signals);
const chainedSkills = buildChain(primarySkill, signals);
const warnings = validateDecision(primarySkill, signals);
const needsClarification: string[] = [];
if (signals.mcp && signals.restApi) {
needsClarification.push('Do you want deterministic REST API automation, AI-agent MCP tooling, or a hybrid of both?');
}
if (primarySkill === 'zoom-general') {
needsClarification.push('Do you need SDK embed behavior, API resource automation, or event ingestion?');
}
const rationale = [
`primary=${primarySkill}`,
`signals=${JSON.stringify(signals)}`,
`chained=${chainedSkills.join(',') || 'none'}`,
];
return {
primarySkill,
chainedSkills,
confidence: confidenceFromSignals(signals),
rationale,
needsClarification,
warnings,
};
}
```
## Handoff Contract (Example Output)
```json
{
"primarySkill": "zoom-meeting-sdk",
"chainedSkills": ["zoom-oauth", "zoom-rest-api", "zoom-webhooks"],
"confidence": 0.9,
"rationale": [
"primary=zoom-meeting-sdk",
"signals={\"meetingEmbed\":true,\"restApi\":true,\"webhooks\":true,...}",
"chained=zoom-oauth,zoom-rest-api,zoom-webhooks"
],
"needsClarification": [],
"warnings": [
"mixed SDK + REST intent; keep SDK as primary and use REST only for resource workflows"
]
}
```
## Error Handling Expectations
- Unknown/low-signal prompts route to `zoom-general` with one clarifying question.
- Conflicting signals do not fail hard; produce warnings and preserve guardrails.
- Routing should be deterministic for the same normalized prompt.
@@ -0,0 +1,94 @@
# OAuth Scopes
OAuth scopes define what your app can access.
## Overview
Scopes are permissions requested during OAuth authorization. Request only the scopes you need.
## IMPORTANT: Scope Types
**Different OAuth types have different scopes available:**
| OAuth Type | Scope Suffix | Access Level | Example |
|------------|--------------|--------------|---------|
| **User OAuth** | (none) | Current user's data only | `meeting:read` |
| **Admin OAuth** | `:admin` | All users in account | `meeting:read:admin` |
| **Server-to-Server (S2S)** | `:admin` | All users in account (no user consent) | `meeting:read:admin` |
### Key Differences
- **User scopes** (`meeting:read`): Access only the authenticated user's data
- **Admin scopes** (`meeting:read:admin`): Access data for ALL users in the account
- **S2S OAuth**: Uses admin-level scopes but doesn't require user login - intended for backend integrations
### Choosing the Right Scope Type
| Use Case | OAuth Type | Scope Example |
|----------|------------|---------------|
| User manages their own meetings | User OAuth | `meeting:write` |
| Admin dashboard for all users | Admin OAuth | `meeting:read:admin` |
| Backend automation (no user login) | Server-to-Server | `meeting:write:admin` |
| Bot that creates meetings for users | Server-to-Server | `meeting:write:admin` |
## Common Scopes
### Meetings
| User Scope | Admin Scope | Description |
|------------|-------------|-------------|
| `meeting:read` | `meeting:read:admin` | View meeting details |
| `meeting:write` | `meeting:write:admin` | Create, update, delete meetings |
| `meeting:master` | `meeting:master:admin` | Full meeting access |
### Users
| User Scope | Admin Scope | Description |
|------------|-------------|-------------|
| `user:read` | `user:read:admin` | View user profile |
| `user:write` | `user:write:admin` | Update user settings |
| `user:master` | `user:master:admin` | Full user access |
### Recordings
| User Scope | Admin Scope | Description |
|------------|-------------|-------------|
| `recording:read` | `recording:read:admin` | View/download recordings |
| `recording:write` | `recording:write:admin` | Delete recordings |
| `recording:master` | `recording:master:admin` | Full recording access |
### Webinars
| User Scope | Admin Scope | Description |
|------------|-------------|-------------|
| `webinar:read` | `webinar:read:admin` | View webinar details |
| `webinar:write` | `webinar:write:admin` | Create, update webinars |
| `webinar:master` | `webinar:master:admin` | Full webinar access |
### Reports
| User Scope | Admin Scope | Description |
|------------|-------------|-------------|
| `report:read` | `report:read:admin` | View reports and analytics |
| `report:master` | `report:master:admin` | Full report access |
## Scope Patterns
| Pattern | Meaning |
|---------|---------|
| `resource:read` | Read-only access (current user) |
| `resource:write` | Read and write access (current user) |
| `resource:master` | Full access including delete (current user) |
| `resource:read:admin` | Read-only access (all account users) |
| `resource:write:admin` | Read and write access (all account users) |
| `resource:master:admin` | Full access including delete (all account users) |
## Best Practices
1. **Request minimum scopes** - Only what you need
2. **Explain to users** - Why you need each scope
3. **Handle denied scopes** - Graceful fallback
## Resources
- **Scopes reference**: https://developers.zoom.us/docs/integrations/oauth-scopes/
@@ -0,0 +1,194 @@
# SDK Logs & Troubleshooting
Collecting SDK logs for debugging and support.
## Official Log Retrieval Guides
**IMPORTANT**: Always refer to the official Zoom log retrieval guides for the most up-to-date instructions:
- **Video SDK Log Retrieval**: https://developers.zoom.us/blog/vsdk-log-retrieval-instructions/
- **Meeting SDK Log Retrieval**: https://developers.zoom.us/blog/msdk-log-retrieval-instructions/
If these URLs are unavailable, search for "zoom sdk log retrieval" to find the current documentation.
## Overview
SDK logs help diagnose issues during development and for Zoom support escalations.
## Enabling Logs
### Web SDK
```javascript
// Enable verbose logging
ZoomMtg.setLogLevel('verbose');
// Or for Video SDK
client.init('en-US', 'CDN', { debug: true });
```
**Web Tracking ID**: For Web SDK troubleshooting, get the **Web Tracking ID** which helps Zoom support trace your session.
**Meeting SDK Web**:
1. Open browser DevTools → **Network** tab
2. Look for a request starting with `info?meetingNumber...`
3. Click on the request and check the **Response Headers**
4. Find the `x-zm-trackingid` header value
5. Copy this ID for support tickets
**Video SDK Web**:
1. Open browser DevTools → **Network** tab
2. Look for a request starting with `lsdk?topic...`
3. Click on the request and check the **Response Headers**
4. Find the `x-zm-trackingid` header value
5. Copy this ID for support tickets
```
Example header:
x-zm-trackingid: v=2.0;clid=us04;rid=WEB_abc123xyz...
```
The Web Tracking ID is essential for Zoom support to investigate Web SDK issues.
**To get help with logs and tracking IDs:**
- **Open a support ticket**: https://devsupport.zoom.us/
- **Post on Developer Forum**: https://devforum.zoom.us/
Include the tracking ID and relevant logs when requesting assistance.
### iOS SDK
```swift
// Set log file path
let initParams = MobileRTCSDKInitParams()
initParams.enableLog = true
initParams.logFilePrefix = "zoom_sdk"
```
### Android SDK
```kotlin
val initParams = ZoomSDKInitParams().apply {
enableLog = true
logSize = 5 // MB
}
```
### Desktop SDKs (Windows/macOS/Linux)
```cpp
initParam.enableLogByDefault = true;
initParam.logFilePrefix = L"zoom_sdk";
```
## Log Locations
| Platform | Default Location |
|----------|------------------|
| iOS | App's Documents directory |
| Android | App's files directory |
| Windows | `%APPDATA%\ZoomSDK\` |
| macOS | `~/Library/Logs/ZoomSDK/` |
| Linux | Working directory |
## Common Issues and Solutions
| Issue | Possible Cause | Solution |
|-------|----------------|----------|
| Join failed | Invalid signature | Check JWT generation (exp should be ~10s after iat, but iat can be up to 2 hours in past) |
| Join failed | Meeting not found | Verify meeting number and that meeting hasn't ended |
| No audio | Permission denied | Request microphone permission before joining |
| No video | Permission denied | Request camera permission before joining |
| Video scales down | Container too small | Ensure container is at least 1280x720 for 720p |
| SharedArrayBuffer error | Missing headers | Add COOP/COEP headers to server |
| Error code 0 | Actually success | Check SDK docs - 0 often means success, not error |
| SDK crash | ProGuard enabled | Disable ProGuard/R8 for Zoom SDK classes |
| DLL not found | Missing files | Copy ALL DLLs from SDK bin folder |
### Debugging Join Failures
```javascript
// Web SDK - enable verbose logging
ZoomMtg.setLogLevel('verbose');
// Check signature
console.log('Signature:', signature);
console.log('Meeting:', meetingNumber);
// Verify callback
client.join({
// ...params
success: (res) => console.log('Join success:', res),
error: (err) => console.error('Join error:', err)
});
```
### Debugging Audio/Video Issues
```javascript
// Check device availability
const devices = await navigator.mediaDevices.enumerateDevices();
console.log('Audio inputs:', devices.filter(d => d.kind === 'audioinput'));
console.log('Video inputs:', devices.filter(d => d.kind === 'videoinput'));
// Check permissions
const micPermission = await navigator.permissions.query({ name: 'microphone' });
const camPermission = await navigator.permissions.query({ name: 'camera' });
console.log('Mic:', micPermission.state);
console.log('Cam:', camPermission.state);
```
### Native SDK Crash Debugging
**iOS**:
```swift
// Enable crash reporting
MobileRTC.shared().setEnableCrashReport(true)
// Get logs
let logPath = MobileRTC.shared().getLogPath()
print("Logs at: \(logPath)")
```
**Android**:
```kotlin
// Check logcat for crashes
adb logcat -s ZoomSDK:V
// Disable ProGuard for Zoom
// In proguard-rules.pro:
-keep class us.zoom.** { *; }
-keep interface us.zoom.** { *; }
```
### Error Code Reference
| Code | Meaning | Platform |
|------|---------|----------|
| 0 | Success (NOT error!) | All |
| 1 | Generic error | All |
| 2 | Invalid argument | All |
| 3 | Invalid token | All |
| 4 | Timeout | All |
| 8 | SDK not authorized | Windows |
| 100000400 | Meeting join failed | Windows |
| MobileRTCMeetError_Success | Success | iOS |
| MEETING_STATUS_INMEETING | In meeting | Android |
## Getting Support
When contacting Zoom support:
1. Include SDK version
2. Attach log files
3. Describe steps to reproduce
4. Include error codes (check if 0 means success!)
## Resources
- **Video SDK Log Retrieval Guide**: https://developers.zoom.us/blog/vsdk-log-retrieval-instructions/
- **Meeting SDK Log Retrieval Guide**: https://developers.zoom.us/blog/msdk-log-retrieval-instructions/
- **Developer support**: https://devsupport.zoom.us/
- **Developer forum**: https://devforum.zoom.us/
> **Note for LLMs/Agents**: If the log retrieval URLs return 404 or have moved, perform a web search for "zoom sdk log retrieval instructions" to find the current documentation.
@@ -0,0 +1,164 @@
# SDK Upgrade Guide
Guide for upgrading Meeting SDK and Video SDK versions.
For customer upgrades from older versions to latest, use:
- [sdk-upgrade-workflow.md](sdk-upgrade-workflow.md) - changelog + RSS, version-by-version migration workflow.
## IMPORTANT: Check the Changelog First
**Before any upgrade, always check the official Zoom changelog:**
**Primary URL**: https://developers.zoom.us/changelog/
If the above URL is unavailable or has moved, search for **"zoom changelog"** or **"zoom developer changelog"** to find the current location.
The changelog contains:
- Latest SDK versions and release dates
- Breaking changes and deprecations
- New features and improvements
- Bug fixes and security patches
## Overview
Zoom releases SDK updates regularly. This guide covers version policy and upgrade procedures.
## Version Policy
- **Major versions** - May contain breaking changes
- **Minor versions** - New features, backward compatible
- **Patch versions** - Bug fixes
## Before Upgrading
1. Read changelog for target version
2. Note breaking changes and deprecations
3. Test in development environment
4. Plan migration for deprecated APIs
## Upgrade Steps
### Web SDK (npm)
```bash
# Check current version
npm list @zoom/meetingsdk
# Update to latest
npm update @zoom/meetingsdk
# Or specific version
npm install @zoom/meetingsdk@2.18.0
```
### Native SDKs
1. Download new SDK from [Marketplace](https://marketplace.zoom.us/) (sign-in required)
2. Replace SDK files in your project
3. Update linker/framework settings if needed
4. Rebuild project
## Common Migration Tasks
### API Signature Changes
When methods change signatures between versions:
```javascript
// Old (v2.x)
client.join({
sdkKey: key,
sdkSecret: secret, // REMOVED in v3.x
meetingNumber: number
});
// New (v3.x) - signature generated server-side
client.join({
sdkKey: key,
signature: serverGeneratedSignature, // NEW
meetingNumber: number
});
```
**Action**: Update to server-side signature generation for security.
### Deprecated Method Replacements
| Old Method | New Method | Version |
|------------|------------|---------|
| `ZoomMtg.init()` | `client.init()` | Web SDK 3.x |
| `startVideo()` | `startVideo()` + `renderVideo()` | Video SDK 1.8+ |
| `getMeetingUUID()` | Use webhook payload | Meeting SDK 2.x |
### New Initialization Requirements
**Meeting SDK Web 3.x**:
```javascript
// Now requires explicit preload
import ZoomMtgEmbedded from '@zoom/meetingsdk/embedded';
const client = ZoomMtgEmbedded.createClient();
// Must init before join
await client.init({
zoomAppRoot: document.getElementById('root'),
language: 'en-US'
});
```
**Video SDK 1.8+**:
```javascript
// Video rendering is now two-step
await stream.startVideo();
await stream.renderVideo(
document.querySelector('#video-canvas'),
myUserId,
1280, 720, 0, 0, 3 // width, height, x, y, quality
);
```
### Breaking Changes Checklist
When upgrading major versions, check:
- [ ] Initialization flow changed?
- [ ] Authentication method changed?
- [ ] Event names/signatures changed?
- [ ] Required permissions changed?
- [ ] Minimum platform version changed?
- [ ] New required headers (COOP/COEP)?
### Testing Upgrade
```bash
# Create upgrade branch
git checkout -b sdk-upgrade-v3
# Update package
npm install @zoom/meetingsdk@latest
# Run tests
npm test
# Test manually
# - Join meeting
# - Audio/video functionality
# - Screen sharing
# - Recording (if used)
# - Custom UI features
```
## Version Support Policy
- **Latest version**: Full support
- **Previous major**: Security fixes only
- **Older versions**: No support, upgrade recommended
## Resources
- **Main Changelog**: https://developers.zoom.us/changelog/ (check here first!)
- **Meeting SDK changelog**: https://developers.zoom.us/changelog/meeting-sdk/
- **Video SDK changelog**: https://developers.zoom.us/changelog/video-sdk/
- **Migration guides**: https://developers.zoom.us/docs/meeting-sdk/web/migrate/
> **Note for LLMs/Agents**: If the changelog URLs return 404 or have moved, perform a web search for "zoom developer changelog" or "zoom sdk changelog" to find the current location. Zoom occasionally restructures their documentation.
@@ -0,0 +1,144 @@
# SDK Upgrade Workflow (Changelog + RSS)
Reusable process for upgrading Zoom SDK integrations from an older customer version to latest with low regression risk.
## Use This When
- Customer is multiple versions behind.
- Breaking changes may exist between current and latest.
- You need a defensible, version-by-version upgrade plan.
## Inputs Required
1. Product and platform
- Example: `Meeting SDK Android`, `Video SDK iOS`, `Contact Center Web`.
2. Current version in production
- Example: `6.3.1`, `2.1.0`.
3. Target version
- Usually latest stable from changelog.
4. Critical features in use
- Example: custom UI, raw data, recording, chat, live transcription, token flow.
## Canonical Source
- Changelog entry point: https://developers.zoom.us/changelog/
## Workflow
### 1) Scope the upgrade lane
- Confirm exact product + platform lane before collecting releases.
- Do not mix lanes (for example, Meeting SDK Web and Meeting SDK iOS must be treated separately).
### 2) Locate the platform-specific RSS feed
From `https://developers.zoom.us/changelog/`:
- Filter by product/platform.
- Find the RSS link for that filtered lane.
- Use only that feed for release collection.
If feed discovery is unclear:
- Open the filtered changelog page and locate the RSS icon/link.
- Confirm feed entries match the same product/platform lane.
### 3) Build the release ledger
Collect all releases from:
- `current_version` (exclusive) up to `target_version` (inclusive), then latest if target is `latest`.
For each release entry capture:
- Version
- Release date
- Release URL
- Breaking/deprecated notes
- Required migration actions
Sort upgrade steps in ascending version order.
### 4) Plan upgrade hops
Default strategy:
- Patch/minor jumps can often be grouped.
- Major changes should be isolated into dedicated hops.
Recommended hop pattern:
1. `current -> next safe checkpoint`
2. `checkpoint -> next major boundary`
3. Repeat until latest
### 5) Extract required actions per hop
For each hop, classify actions under:
- Auth/token contract changes
- API renames/signature changes
- Initialization/lifecycle changes
- Event payload/callback changes
- Build/dependency/runtime requirements
- Feature removals/deprecations
### 6) Apply compatibility guards
- Wrap renamed/deprecated calls behind adapters.
- Keep temporary compatibility mappings for payload changes.
- Add feature flags for behavior toggles when needed.
### 7) Validate each hop before continuing
Minimum validation set:
- SDK init/auth
- Join/start/session entry flow
- Core media flows (audio/video/share) if applicable
- Critical product-specific features used by customer
- Cleanup/leave/disconnect behavior
Do not skip to next hop if the current hop is unstable.
### 8) Produce final upgrade package
Deliver:
- Step-by-step upgrade matrix
- Per-hop code/config change list
- Deprecated-to-replacement map
- Risks and rollback notes
- Final target-state checklist
## Output Template
```markdown
## Upgrade Plan: <product/platform>
- Current: <x.y.z>
- Target: <a.b.c or latest>
- Source feed: <rss_url>
### Hop 1: <x.y.z -> x.y+1.z>
- Release notes:
- <url>
- Breaking/deprecations:
- <item>
- Required changes:
- <item>
- Validation:
- <item>
### Hop 2: <...>
...
## Deprecated -> Replacement Map
- <old> -> <new>
## Risks
- <risk>
## Rollback
- <rollback step>
```
## Operating Rules
- Never assume only latest release notes are sufficient.
- Always process intermediate releases between customer version and target.
- Prefer smallest-risk path over fastest path for production upgrades.
@@ -0,0 +1,392 @@
# AI Companion Integration
Integrate with Zoom AI Companion for meeting summaries, transcripts, and AI-powered features.
## Overview
Zoom AI Companion provides AI-powered features including:
- Meeting summaries (auto-generated)
- Meeting transcripts
- Real-time transcription
- Smart recording highlights
- Conversation archives
## What's Available via API
| Feature | API Access | Method |
|---------|------------|--------|
| Meeting Summaries | ✅ Yes | REST API |
| Meeting Transcripts | ✅ Yes | REST API (Cloud Recording) |
| Real-Time Transcripts | ✅ Yes | RTMS SDK |
| AI Companion Panel | ⚠️ Limited | Archive only |
| Conversation Archives | ✅ Yes | REST API |
| AI Controls in Meeting | ✅ Yes | Meeting SDK |
## Skills Needed
| Use Case | Skills |
|----------|--------|
| AI-agent search and tool invocation over Zoom meeting context | **zoom-mcp** |
| Get meeting summaries after meeting | **zoom-rest-api** |
| Get meeting transcripts in deterministic backend pipeline | **zoom-rest-api** + **zoom-webhooks** |
| Real-time transcript streaming | **rtms** |
| Control AI features in embedded meetings | **zoom-meeting-sdk** |
## Routing Modes
- Use **zoom-rest-api** when you need deterministic backend jobs, strict retry behavior, and explicit endpoint control.
- Use **zoom-mcp** when an AI system must discover and invoke Zoom tools dynamically.
- Use both when your architecture requires stable API automation plus AI-driven retrieval and assistance.
---
## Get Meeting Summary (REST API)
### Endpoint
```
GET /v2/meetings/{meetingUUID}/meeting_summary
```
### Prerequisites
1. Meeting Summary feature enabled in account settings
2. Server-to-Server OAuth app with admin scopes
3. Meeting must have ended with summary generated
### Example
```javascript
// Get meeting summary
async function getMeetingSummary(meetingUUID) {
// Double-encode UUID if it contains / or //
const encodedUUID = meetingUUID.startsWith('/')
? encodeURIComponent(encodeURIComponent(meetingUUID))
: encodeURIComponent(meetingUUID);
const response = await fetch(
`https://api.zoom.us/v2/meetings/${encodedUUID}/meeting_summary`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
return response.json();
}
// Response example
{
"meeting_uuid": "abc123...",
"meeting_id": 12345678901,
"meeting_topic": "Weekly Team Sync",
"meeting_start_time": "2024-01-15T10:00:00Z",
"meeting_end_time": "2024-01-15T10:45:00Z",
"summary_start_time": "2024-01-15T10:00:00Z",
"summary_end_time": "2024-01-15T10:45:00Z",
"summary_content": {
"summary": "The team discussed Q1 roadmap priorities...",
"next_steps": [
"John to finalize design specs by Friday",
"Sarah to schedule customer interviews"
],
"keywords": ["roadmap", "Q1", "design", "customers"]
}
}
```
### Important Notes
- **Admin role required**: Standard users may not have access
- **Make app admin-managed**: Resolves permission issues
- **UUID encoding**: Double-encode UUIDs starting with `/`
---
## Get Meeting Transcript (REST API)
Transcripts are accessed via the Cloud Recording API.
### Endpoint
```
GET /v2/meetings/{meetingId}/recordings
```
### Example
```javascript
async function getMeetingTranscript(meetingId) {
const response = await fetch(
`https://api.zoom.us/v2/meetings/${meetingId}/recordings`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const data = await response.json();
// Find transcript files
const transcriptFiles = data.recording_files.filter(
file => file.file_type === 'TRANSCRIPT' ||
file.file_type === 'CC' ||
file.file_type === 'SUMMARY'
);
// Download transcript
for (const file of transcriptFiles) {
const transcriptResponse = await fetch(file.download_url, {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
if (file.file_extension === 'VTT') {
const vttContent = await transcriptResponse.text();
console.log('VTT Transcript:', vttContent);
} else if (file.file_extension === 'JSON') {
const jsonContent = await transcriptResponse.json();
console.log('JSON Transcript:', jsonContent);
}
}
return transcriptFiles;
}
```
### Transcript File Types
| File Type | Extension | Description |
|-----------|-----------|-------------|
| `TRANSCRIPT` | VTT, JSON | Full meeting transcript |
| `CC` | VTT | Closed captions |
| `SUMMARY` | JSON | AI-generated summary |
---
## Webhooks for AI Content
Listen for when AI content is ready:
```javascript
// Webhook handler
app.post('/webhook', (req, res) => {
const { event, payload } = req.body;
switch (event) {
case 'recording.transcript_completed':
// Transcript is ready
console.log('Transcript ready for meeting:', payload.object.uuid);
fetchAndStoreTranscript(payload.object.uuid);
break;
case 'recording.completed':
// Recording processing complete (may include summary)
console.log('Recording ready:', payload.object.uuid);
break;
case 'meeting.ended':
// Meeting ended - summary will be generated soon
console.log('Meeting ended:', payload.object.uuid);
break;
}
res.sendStatus(200);
});
```
---
## Real-Time Transcripts (RTMS)
For live transcript streaming during meetings, use RTMS SDK.
### Prerequisites
- RTMS access approval from Zoom
- Server infrastructure for WebSocket connections
### Example
```javascript
import { RTMSClient } from "@zoom/rtms";
const client = new RTMSClient({
clientId: process.env.ZOOM_CLIENT_ID,
clientSecret: process.env.ZOOM_CLIENT_SECRET,
secretToken: process.env.ZOOM_SECRET_TOKEN
});
// Connect to meeting
await client.joinMeeting({
meetingUuid: meetingUUID,
streamId: streamId,
serverUrl: "wss://rtms.zoom.us"
});
// Listen for transcript events
client.on('transcript', (data) => {
console.log(`[${data.speakerName}]: ${data.text}`);
// Process real-time transcript
// - Send to AI for sentiment analysis
// - Display live captions
// - Log for compliance
});
```
See **rtms** skill for full RTMS documentation.
---
## Meeting SDK - AI Companion Controls
Control AI Companion features in embedded meetings.
### Web SDK
```javascript
// Check if AI Companion is available
const aiCompanionStatus = ZoomMtg.getAICompanionStatus();
// AI Companion features are controlled by meeting settings
// The SDK respects account/meeting-level AI Companion settings
```
### Native SDKs (Android/iOS/Desktop)
Use `InMeetingAICompanionController`:
```java
// Android example
InMeetingAICompanionController aiController =
ZoomSDK.getInstance().getInMeetingService().getInMeetingAICompanionController();
// Check AI Companion status
boolean isEnabled = aiController.isAICompanionEnabled();
// Get available features
AICompanionFeature[] features = aiController.getAvailableFeatures();
// Features: QUERY, SMART_SUMMARY, SMART_RECORDING
```
```swift
// iOS example
let aiController = MobileRTC.shared().getMeetingService()?.getInMeetingAICompanionController()
if let isEnabled = aiController?.isAICompanionEnabled() {
print("AI Companion enabled: \(isEnabled)")
}
```
### Feature Constants
| Feature | Description |
|---------|-------------|
| `QUERY` | Ask AI Companion questions |
| `SMART_SUMMARY` | Meeting summary generation |
| `SMART_RECORDING` | Smart recording highlights |
---
## Conversation Archives API
Archive AI Companion panel conversations (new September 2025).
```javascript
// Get conversation archives
async function getConversationArchives(userId) {
const response = await fetch(
`https://api.zoom.us/v2/users/${userId}/conversation_archive`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
return response.json();
}
```
---
## Common Integration Patterns
### Pattern 1: Post-Meeting Summary Pipeline
```javascript
// 1. Listen for meeting end
webhooks.on('meeting.ended', async (meeting) => {
// 2. Wait for transcript to be ready (or use webhook)
await delay(60000); // Processing time varies
// 3. Fetch summary
const summary = await getMeetingSummary(meeting.uuid);
// 4. Store or distribute
await saveSummaryToDatabase(summary);
await sendSummaryToParticipants(meeting.participants, summary);
});
```
### Pattern 2: Real-Time AI Processing
```javascript
// Using RTMS for live processing
rtmsClient.on('transcript', async (data) => {
// Send to your AI service for analysis
const sentiment = await analyzesentiment(data.text);
const actionItems = await extractActionItems(data.text);
// Update live dashboard
updateDashboard({ sentiment, actionItems });
});
```
### Pattern 3: Compliance Archival
```javascript
// Archive all AI-generated content
async function archiveMeetingAIContent(meetingId) {
const [summary, transcript, archives] = await Promise.all([
getMeetingSummary(meetingId),
getMeetingTranscript(meetingId),
getConversationArchives(meetingId)
]);
await complianceStore.save({
meetingId,
summary,
transcript,
aiConversations: archives,
archivedAt: new Date()
});
}
```
---
## Required Scopes
| Scope | Description |
|-------|-------------|
| `meeting:read:admin` | Read meeting data including summaries |
| `recording:read:admin` | Access recordings and transcripts |
| `user:read:admin` | Read user data for archives |
---
## Limitations
| Limitation | Notes |
|------------|-------|
| AI Companion Panel | Most panel features NOT available via API |
| Admin access | Some endpoints require admin role |
| Processing time | Summaries/transcripts not instant after meeting |
| RTMS approval | Real-time access requires Zoom approval |
| Bot restrictions | Meeting SDK does NOT support bots (use RTMS) |
---
## Resources
- **AI Companion APIs**: https://developers.zoom.us/docs/api/ai-companion/
- **Meeting Summary API**: https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/meetingSummary
- **Cloud Recording API**: https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#tag/Cloud-Recording
- **RTMS Documentation**: https://developers.zoom.us/docs/rtms/
@@ -0,0 +1,224 @@
# AI Integration
Build real-time AI features for Zoom meetings - sentiment analysis, summarization, and more.
## Overview
Integrate AI/ML capabilities with Zoom meetings using real-time media streams for live transcription, sentiment analysis, meeting summarization, and intelligent automation.
## Skills Needed
- **rtms** - Primary (real-time media access)
- **zoom-meeting-sdk** (Linux) - For meeting bots
## AI Use Cases
| Use Case | Input | Output |
|----------|-------|--------|
| Transcription | Audio stream | Real-time text |
| Sentiment | Audio/transcript | Mood indicators |
| Summarization | Transcript | Meeting summary |
| Action items | Transcript | Task list |
| Translation | Audio/transcript | Multi-language |
## Architecture
```
AI Integration Architecture:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Zoom │────▶│ RTMS / │────▶│ AI/ML │
│ Meeting │ │ Bot SDK │ │ Pipeline │
└─────────────┘ └─────────────┘ └─────────────┘
Audio/Video/Transcript
```
## Prerequisites
- RTMS access or Meeting SDK (Linux)
- AI/ML service (OpenAI, Azure, custom)
- Real-time processing infrastructure
## Common Tasks
### Setting Up RTMS for AI
```javascript
// 1. Configure RTMS app in Marketplace
// Enable: Audio stream, Video stream, Transcript stream
// 2. Handle webhook to get connection details
app.post('/webhook', (req, res) => {
if (req.body.event === 'meeting.rtms_started') {
const { server_urls, stream_id, signature } = req.body.payload;
// Start AI processing pipeline
aiPipeline.connect({
url: server_urls[0],
streamId: stream_id,
signature: signature
});
}
res.status(200).send();
});
```
### Real-Time Transcription Pipeline
```javascript
// Option 1: Use Zoom's built-in transcript (via RTMS)
rtmsClient.on('transcript', (data) => {
const { text, speaker_id, is_final } = data;
if (is_final) {
transcriptStore.append(speaker_id, text);
}
});
// Option 2: Send audio to external STT (Whisper, Deepgram)
const deepgram = new Deepgram(DEEPGRAM_KEY);
const transcriber = deepgram.transcription.live({
punctuate: true,
interim_results: true,
language: 'en-US'
});
rtmsClient.on('audio', (audioChunk) => {
transcriber.send(audioChunk);
});
transcriber.on('transcriptReceived', (data) => {
const transcript = data.channel.alternatives[0].transcript;
processTranscript(transcript);
});
```
### Sentiment Analysis Integration
```javascript
// Real-time sentiment on transcript segments
async function analyzeSentiment(text) {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{
role: 'system',
content: 'Analyze sentiment. Return JSON: {sentiment: "positive|negative|neutral", confidence: 0-1, emotions: []}'
}, {
role: 'user',
content: text
}],
response_format: { type: 'json_object' }
});
return JSON.parse(response.choices[0].message.content);
}
// Track sentiment over time
class SentimentTracker {
constructor() {
this.history = [];
}
async process(transcript) {
const sentiment = await analyzeSentiment(transcript);
this.history.push({
timestamp: Date.now(),
text: transcript,
...sentiment
});
// Alert on negative sentiment
if (sentiment.sentiment === 'negative' && sentiment.confidence > 0.8) {
this.emit('alert', { type: 'negative_sentiment', data: sentiment });
}
}
getOverallSentiment() {
// Aggregate sentiment over meeting duration
}
}
```
### Meeting Summarization
```javascript
// Generate summary after meeting ends
async function generateMeetingSummary(fullTranscript) {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{
role: 'system',
content: `Summarize this meeting transcript. Include:
1. Key discussion points
2. Decisions made
3. Action items with owners
4. Follow-up needed`
}, {
role: 'user',
content: fullTranscript
}]
});
return response.choices[0].message.content;
}
// Extract action items
async function extractActionItems(transcript) {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{
role: 'system',
content: 'Extract action items as JSON array: [{task, owner, deadline}]'
}, {
role: 'user',
content: transcript
}],
response_format: { type: 'json_object' }
});
return JSON.parse(response.choices[0].message.content);
}
```
### Latency Considerations
| Processing Type | Target Latency | Recommendation |
|-----------------|----------------|----------------|
| Live captions | < 500ms | Use streaming STT (Deepgram, AssemblyAI) |
| Sentiment | < 2s | Batch every 10-15 seconds |
| Summarization | Post-meeting | Process after meeting ends |
| Action items | < 5s | Process paragraph by paragraph |
### Example AI Pipeline Architecture
```
┌─────────────────────────────────────────────────────────┐
│ RTMS WebSocket │
└─────────────┬───────────────┬───────────────┬──────────┘
│ │ │
Audio Stream Video Stream Transcript
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌──────────┐ ┌───────────────┐
│ Speech-to-Text│ │Face/OCR │ │ NLP Pipeline │
│ (Deepgram) │ │Detection │ │ (OpenAI GPT) │
└───────┬───────┘ └────┬─────┘ └───────┬───────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────┐
│ Results Aggregator │
│ - Transcripts - Sentiment - Action Items │
└─────────────────────┬───────────────────────────┘
┌───────────────┐
│ Storage / │
│ Dashboard │
└───────────────┘
```
## Resources
- **RTMS docs**: https://developers.zoom.us/docs/rtms/
- **Meeting SDK Linux**: https://developers.zoom.us/docs/meeting-sdk/linux/
- **Deepgram**: https://deepgram.com/
- **OpenAI API**: https://platform.openai.com/docs
@@ -0,0 +1,83 @@
# APIs vs MCP Routing
Decide whether to route a request to Zoom APIs, Zoom MCP, or both.
## Overview
Zoom APIs and Zoom MCP are complementary:
- Zoom APIs are best for deterministic system integrations.
- Zoom MCP is best for AI-driven tool-based workflows.
- Use both for enterprise AI systems that need a stable automation core and an adaptive AI layer.
- Zoom-hosted MCP follows a product-scoped server model; access is OAuth-scoped and governed.
## Decision Matrix
| Primary requirement | Route | Notes |
|---------------------|-------|-------|
| Deterministic automation, configuration, reporting, scheduled jobs, strict retries/error handling | **zoom-rest-api** | Direct control over requests, retries, and idempotency |
| AI interaction, dynamic tool discovery, AI Companion workflows, external AI interoperability | **zoom-mcp** | Agent chooses tools contextually through MCP |
| High-volume production automation plus AI assistant workflows | **zoom-rest-api + zoom-mcp** | Keep core actions in APIs; expose curated tool surfaces via MCP |
## Typical Routing Examples
| User request | Route |
|--------------|-------|
| "Create meetings nightly and sync metrics to BI" | **zoom-rest-api** |
| "Let my assistant search meeting content and fetch transcripts" | **zoom-mcp** |
| "Automate meeting lifecycle, then let agents answer questions from summaries" | **zoom-rest-api + zoom-mcp** |
## Chaining Patterns
### Pattern A: API-only deterministic backend
1. `zoom-oauth` for app auth/token lifecycle.
2. `zoom-rest-api` for create/read/update/reporting endpoints.
3. `zoom-webhooks` for async event processing if needed.
### Pattern B: MCP-first AI tool workflows
1. `zoom-oauth` for user OAuth token required by MCP server.
2. `zoom-mcp` for semantic meeting search, summaries, recordings/transcripts, and tool invocation.
### Pattern C: Hybrid enterprise AI architecture
1. `zoom-rest-api` handles provisioning, policy/configuration, and scheduled ingestion jobs.
2. `zoom-webhooks` or `zoom-websockets` handles event ingestion.
3. `zoom-mcp` exposes curated higher-level tools for AI Companion or external agents.
## MCP Fit Checklist (FAQ-Aligned)
Use `zoom-mcp` when you are:
- Building custom tools for AI models.
- Creating data integration services for AI assistants.
- Developing specialized assistants that need tool discovery.
- Extending AI capabilities with external services via MCP.
- Building enterprise AI solutions that need interoperable agent tooling.
## MCP Client and Transport Constraints
- Zoom remote MCP server is consumed over Streamable HTTP/SSE.
- Typical supported MCP clients include Claude and VS Code MCP-capable tooling.
- A local stdio mode may be available depending on client setup, but remote Zoom MCP routing assumes HTTP/SSE transport.
- Endpoint model is shared by instance/cluster; do not assume per-customer dedicated endpoint generation.
- MCP server surfaces can be product-scoped (for example Meetings, Team Chat, Whiteboard). Route by product when those surfaces are available.
## Routing Guardrails
- Do not route deterministic backend automation to MCP only.
- Do not route AI-agent tool discovery tasks to REST only.
- Prefer hybrid routing when both deterministic backend operations and AI-driven interactions are required.
## Related Skills
- [zoom-rest-api](../../rest-api/SKILL.md)
- [zoom-mcp](../../zoom-mcp/SKILL.md)
- [zoom-oauth](../../oauth/SKILL.md)
- [zoom-webhooks](../../webhooks/SKILL.md)
- [zoom-websockets](../../websockets/SKILL.md)
## Source
- https://developers.zoom.us/docs/mcp/library/resources/apis-vs-mcp/
@@ -0,0 +1,221 @@
# Backend Automation with Server-to-Server OAuth
Automate Zoom account operations using Server-to-Server OAuth for machine-to-machine authentication.
## Scenario
You're building a backend service that needs to:
- Automatically create and manage meetings for your organization
- Generate meeting reports
- Provision and deprovision users
- No user interaction required
- Account-wide API access
## Required Skills
1. **oauth** - S2S OAuth token management
2. **zoom-rest-api** - Account management endpoints
## Architecture
```
Cron Job / Backend Service
Token Cache (Redis)
Zoom APIs (account-wide access)
```
## Implementation
### 1. S2S OAuth Setup (oauth)
**Configure app in Zoom Marketplace:**
- App Type: Server-to-Server OAuth
- Add required scopes: `meeting:write:admin`, `user:write:admin`, `report:read:admin`
- Get credentials: Account ID, Client ID, Client Secret
**See:** `oauth/concepts/oauth-flows.md#server-to-server-s2s-oauth`
### 2. Token Management with Redis (oauth)
```javascript
const redis = require('redis');
const client = redis.createClient();
async function getZoomToken() {
// Check cache first
let token = await client.get('zoom_s2s_token');
if (!token) {
// Request new token
const response = await axios.post(
'https://zoom.us/oauth/token',
'grant_type=account_credentials&account_id=' + ACCOUNT_ID,
{
headers: {
'Authorization': 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')
}
}
);
token = response.data.access_token;
// Cache with TTL (10 second buffer before actual expiration)
await client.setex('zoom_s2s_token', response.data.expires_in - 10, token);
}
return token;
}
```
**See:** `oauth/examples/s2s-oauth-redis.md`
### 3. Automated User Provisioning (zoom-rest-api)
```javascript
// Daily cron job to sync users
cron.schedule('0 0 * * *', async () => {
const token = await getZoomToken();
const newUsers = await getNewUsersFromHR();
for (const user of newUsers) {
await axios.post(
'https://api.zoom.us/v2/users',
{
action: 'create',
user_info: {
email: user.email,
type: 1,
first_name: user.firstName,
last_name: user.lastName
}
},
{
headers: { Authorization: `Bearer ${token}` }
}
);
}
});
```
**Chain to:** `zoom-rest-api` for endpoint details
### 4. Meeting Reports (zoom-rest-api)
```javascript
// Generate weekly meeting reports
async function generateWeeklyReport() {
const token = await getZoomToken();
const response = await axios.get(
'https://api.zoom.us/v2/report/users',
{
params: {
from: startOfWeek(),
to: endOfWeek()
},
headers: { Authorization: `Bearer ${token}` }
}
);
return response.data.users;
}
```
**Chain to:** `zoom-rest-api` reporting endpoints
## Production Deployment
### Docker Setup (oauth)
```yaml
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
automation-service:
build: .
environment:
- ZOOM_ACCOUNT_ID=${ZOOM_ACCOUNT_ID}
- ZOOM_CLIENT_ID=${ZOOM_CLIENT_ID}
- ZOOM_CLIENT_SECRET=${ZOOM_CLIENT_SECRET}
- REDIS_URL=redis://redis:6379
depends_on:
- redis
```
**See:** `oauth/examples/s2s-oauth-redis.md#docker-deployment`
## Error Handling
### Token Errors (oauth)
```javascript
try {
const token = await getZoomToken();
} catch (error) {
if (error.response?.data?.error === 'invalid_client') {
// Invalid credentials
logger.error('Invalid Zoom credentials');
alertOps('Zoom integration broken - check credentials');
}
}
```
**See:** `oauth/troubleshooting/common-errors.md`
### Rate Limiting (zoom-rest-api)
```javascript
// Implement retry logic for rate limits
const retryRequest = async (fn, retries = 3) => {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (error.response?.status === 429) {
// Rate limited - wait and retry
await sleep(Math.pow(2, i) * 1000);
continue;
}
throw error;
}
}
};
```
**Chain to:** `zoom-rest-api` for rate limit details
## Testing
```javascript
// Test S2S token acquisition
describe('S2S OAuth', () => {
it('should get valid access token', async () => {
const token = await getZoomToken();
expect(token).toMatch(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/);
});
it('should cache token in Redis', async () => {
await getZoomToken();
const cached = await client.get('zoom_s2s_token');
expect(cached).toBeTruthy();
});
});
```
## Related Use Cases
- `meeting-automation.md` - Advanced meeting workflows
- `usage-reporting-analytics.md` - Account usage analytics
- `user-and-meeting-creation.md` - Bulk operations
## Skills Used
- **oauth** (primary) - S2S OAuth, token caching
- **zoom-rest-api** - Account management, reporting
- **webhooks** - Real-time event notifications
@@ -0,0 +1,89 @@
# Collaborative Apps
Build real-time shared experiences across meeting participants.
## Overview
Collaborative Zoom Apps let multiple participants interact with the same shared state simultaneously - like Google Docs for Zoom meetings. Examples: shared whiteboards, polls, text editors, dashboards.
## Skills Needed
- **zoom-apps-sdk** (Collaborate Mode, App Communication) - Primary
- **oauth** - Authentication
## Synchronization Patterns
| Pattern | Technology | Best For | Complexity |
|---------|-----------|----------|------------|
| **SDK messaging** | connect() + postMessage() | Simple state (counters, toggles) | Low |
| **Server relay** | Socket.io / WebSocket | Polls, games, dashboards | Medium |
| **CRDT sync** | Y.js + WebRTC | Text editors, whiteboards | High |
### Pattern 1: SDK Messaging (Simplest)
No server needed for state sync:
```javascript
await zoomSdk.connect();
// Send state change
await zoomSdk.postMessage({
payload: JSON.stringify({ type: 'vote', option: 'A' })
});
// Receive state changes
zoomSdk.addEventListener('onMessage', (event) => {
const data = JSON.parse(event.payload);
applyChange(data);
});
```
### Pattern 2: Server Relay
Your backend is the source of truth:
```javascript
const socket = io('https://your-server.com');
const { meetingUUID } = await zoomSdk.getMeetingUUID();
socket.emit('join', { room: meetingUUID });
socket.on('state-update', (state) => renderApp(state));
socket.emit('action', { type: 'vote', option: 'A' });
```
### Pattern 3: CRDT (Conflict-Free)
Best for concurrent editing (text, drawings):
```javascript
import * as Y from 'yjs';
import { WebrtcProvider } from 'y-webrtc';
const { meetingUUID } = await zoomSdk.getMeetingUUID();
const ydoc = new Y.Doc();
const provider = new WebrtcProvider(meetingUUID, ydoc);
// Changes sync automatically via CRDT
```
## Meeting UUID as Room ID
Use `getMeetingUUID()` as the room identifier for state synchronization:
```javascript
const { meetingUUID } = await zoomSdk.getMeetingUUID();
// Same UUID for all participants in the same meeting/room
// Different UUID in breakout rooms
```
## Detailed Guides
- **[Collaborate Mode Example](../../zoom-apps-sdk/examples/collaborate-mode.md)** - Complete implementation
- **[App Communication](../../zoom-apps-sdk/examples/app-communication.md)** - Instance messaging
- **[Breakout Rooms](../../zoom-apps-sdk/examples/breakout-rooms.md)** - Cross-room state sync
- **Sample app**: https://github.com/zoom/zoomapps-texteditor-vuejs
## Skill Chain
```
zoom-apps-sdk (Collaborate + Communication) --> oauth (authorization)
```
@@ -0,0 +1,36 @@
# Contact Center App Lifecycle and Context Switching
Build a Contact Center app that survives engagement switching without losing in-progress work.
## Skills Needed
- `contact-center`
- `zoom-apps-sdk`
- `zoom-oauth` (if backend identity mapping is required)
## Problem
Agents can switch between active engagements. Your app instance may remain alive while visible context changes. If state is global rather than engagement-scoped, data corruption and agent frustration follow.
## Recommended Pattern
1. Configure SDK with engagement capabilities.
2. Query initial context and status.
3. Subscribe to engagement context and status change events.
4. Store drafts and workflow state by `engagementId`.
5. On context switch, load the target engagement state.
6. On end state, finalize or clear that engagement data.
## Failure Modes To Avoid
- Single shared draft object for all engagements.
- Late event subscription after user interaction starts.
- Hard cleanup on tab switch instead of engagement end.
- Assuming visibility equals process lifetime.
## Implementation References
- `../../contact-center/web/examples/app-context-and-state.md`
- `../../contact-center/concepts/architecture-and-lifecycle.md`
- `../../contact-center/RUNBOOK.md`
@@ -0,0 +1,39 @@
# Contact Center Integration
Build support and engagement workflows with Zoom Contact Center across app, web, and mobile surfaces.
## Skills Needed
- `contact-center` (primary)
- `zoom-apps-sdk` (for Contact Center apps in Zoom client)
- `zoom-rest-api` (for Contact Center API automation)
- `zoom-oauth` (for authorization patterns)
## Choose the Surface
1. Contact Center app in Zoom client:
- Use engagement APIs/events and state by `engagementId`.
2. Website embed:
- Use campaign/web SDK scripts with readiness gating.
3. Native mobile:
- Use Android/iOS SDK service lifecycle patterns.
## Core Architecture
1. Initialize context and identity.
2. Start channel service (chat/video/ZVA/scheduled callback).
3. Handle engagement events and context switches.
4. Persist engagement-scoped workflow state.
5. End and cleanup channel services.
## High-Value Use Cases
- Agent notes app keyed by engagement.
- CRM integration with Smart Embed events.
- Campaign-driven routing to chat/video channels.
- Native app rejoin flow for dropped video sessions.
## Where to Go Next
- `../../contact-center/SKILL.md`
- `../../contact-center/RUNBOOK.md`

Some files were not shown because too many files have changed in this diff Show More