Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9f4aa54
chore(01-01): scaffold @chat-adapter/zoom package configuration
shishirsharma Apr 8, 2026
c8e52ab
feat(01-01): add ZoomAdapter type definitions and test stubs
shishirsharma Apr 8, 2026
e54b4c5
test(01-02): add failing webhook verification tests (RED)
shishirsharma Apr 8, 2026
637106b
feat(01-02): implement handleWebhook and verifySignature on ZoomAdapt…
shishirsharma Apr 8, 2026
dc2c6a6
feat(01-foundation-03): implement S2S OAuth token caching and auth tests
shishirsharma Apr 8, 2026
3f60d26
chore(01-foundation-03): build verification and full validate pass
shishirsharma Apr 8, 2026
98606cc
test(02-W0): add markdown.test.ts stub with FMT-01/02/03 it.todo blocks
shishirsharma Apr 8, 2026
b197e8b
feat(02-inbound-01): implement Zoom event routing with TDD (WBHK-04, …
shishirsharma Apr 8, 2026
60cfbf2
feat(02-inbound-02): implement ZoomFormatConverter with full test cov…
shishirsharma Apr 8, 2026
90d9d70
feat(02-inbound-02): wire ZoomFormatConverter into ZoomAdapter
shishirsharma Apr 8, 2026
5576fef
test(03-01): add failing tests for zoomFetch helper and postMessage (…
shishirsharma Apr 8, 2026
c3e559c
feat(03-01): implement zoomFetch() helper and postMessage() for Zoom …
shishirsharma Apr 8, 2026
b03c300
test(03-02): add failing tests for editMessage and deleteMessage (MSG…
shishirsharma Apr 8, 2026
c1ee37e
feat(03-02): implement editMessage() and deleteMessage() (MSG-03, MSG…
shishirsharma Apr 8, 2026
0650415
chore(04-01): add @chat-adapter/zoom dep and whitelist to integration…
shishirsharma Apr 8, 2026
910367f
feat(04-01): add Zoom replay fixtures, zoom-utils, and replay test
shishirsharma Apr 8, 2026
12133aa
feat(04-02): add README for @chat-adapter/zoom
shishirsharma Apr 8, 2026
e43426f
fix(04-02): resolve validation failures from auto-fix deviations
shishirsharma Apr 8, 2026
d0c97fc
fix(05-01): correct DM thread ID format in Zoom README (PKG-04)
shishirsharma Apr 9, 2026
d95d68f
test(05-01): add Subscribe Flow - Zoom THRD-02 integration test
shishirsharma Apr 9, 2026
7dcbabc
feat(06-01): add ZoomMessageWithReply interface and accountId JSDoc t…
shishirsharma Apr 9, 2026
49f9525
fix(06-01): replace anonymous inline cast with named ZoomMessageWithR…
shishirsharma Apr 9, 2026
9708569
chore(06-01): sort ZoomMessageWithReply interface members alphabetica…
shishirsharma Apr 9, 2026
a87d104
chore(07-01): remove @zoom/rivet ghost dependency
shishirsharma Apr 9, 2026
38f2d3e
chore: ignore .planning/ directory (local GSD planning files)
shishirsharma Apr 10, 2026
211ef1f
feat(zoom): add @chat-adapter/zoom to docs, SKILL.md, and changeset
shishirsharma Apr 10, 2026
a2aecb2
fix(zoom): runtime fixes discovered during local testing
shishirsharma Apr 11, 2026
16f51a8
fix(zoom): styled content body and emoji rendering for postMessage
shishirsharma Apr 11, 2026
70ae94b
fix(zoom): address PR review — correct editMessage/deleteMessage endp…
shishirsharma Apr 11, 2026
b8d7467
fix(zoom): add user_jid to postMessage, editMessage, deleteMessage
shishirsharma Apr 11, 2026
9ec0fda
docs(zoom): improve setup steps and add troubleshooting section
shishirsharma Apr 11, 2026
fcf379d
fix(zoom): startTyping is a silent no-op (Zoom has no typing indicato…
shishirsharma Apr 11, 2026
38b3f55
fix(zoom): set isMention=true on bot_notification messages
shishirsharma Apr 11, 2026
980eea8
fix(zoom): set isMention=true on team_chat.app_mention messages
shishirsharma Apr 11, 2026
10ff791
docs(zoom): document thread subscription limitation (verified against…
shishirsharma Apr 11, 2026
a5b62d5
fix(zoom): update replay tests for isMention=true routing and fix lint
shishirsharma Apr 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-zoom-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chat-adapter/zoom": minor
---

Add Zoom Team Chat adapter (`@chat-adapter/zoom`) with webhook verification (CRC challenge + HMAC-SHA256), S2S OAuth chatbot token caching, inbound event parsing for `bot_notification` and `team_chat.app_mention`, outbound message posting/editing/deletion, bidirectional Zoom markdown ↔ mdast format conversion, and integration test fixtures.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@ todo/
next-env.d.ts
.source

packages/chat/docs
packages/chat/docs

# GSD planning (local-only, not committed)
.planning/
4 changes: 4 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Route @zoom scope to public npm registry
# The global npmrc points to Zoom's internal Artifactory, which doesn't
# serve the @zoom/rivet tarballs in this environment.
@zoom:registry=https://registry.npmjs.org/
10 changes: 10 additions & 0 deletions apps/docs/adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@
"beta": true,
"readme": "https://github.com/vercel/chat/tree/main/packages/adapter-whatsapp"
},
{
"name": "Zoom",
"slug": "zoom",
"type": "platform",
"description": "Build bots for Zoom Team Chat with webhook verification, message threading, and bidirectional markdown formatting.",
"packageName": "@chat-adapter/zoom",
"icon": "zoom",
"beta": true,
"readme": "https://github.com/vercel/chat/tree/main/packages/adapter-zoom"
},
{
"name": "Redis",
"slug": "redis",
Expand Down
197 changes: 197 additions & 0 deletions packages/adapter-zoom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# @chat-adapter/zoom

[![npm version](https://img.shields.io/npm/v/@chat-adapter/zoom)](https://www.npmjs.com/package/@chat-adapter/zoom)
[![npm downloads](https://img.shields.io/npm/dm/@chat-adapter/zoom)](https://www.npmjs.com/package/@chat-adapter/zoom)

Zoom Team Chat adapter for [Chat SDK](https://chat-sdk.dev)

## Installation

```bash
pnpm add @chat-adapter/zoom
```

## Usage

```typescript
import { Chat } from "chat";
import { createZoomAdapter } from "@chat-adapter/zoom";

const bot = new Chat({
userName: "mybot",
adapters: {
zoom: createZoomAdapter(),
},
});

bot.onNewMention(async (thread, message) => {
await thread.post("Hello from Zoom!");
});
```

When using `createZoomAdapter()` without arguments, credentials are auto-detected from environment variables.

## Zoom Marketplace app setup

### Prerequisites

- Zoom account with developer access
- A deployed URL or ngrok tunnel for local testing (e.g. `https://your-domain.com`)

### Steps

1. **Create the app**
- Go to [marketplace.zoom.us](https://marketplace.zoom.us) → **Develop** → **Build App**
- Select **General App**
- In **Basic Information**, set management type to **Admin-managed**

2. **Get core credentials** — from **Basic Information** → **App Credentials**
- Copy **Client ID** → `ZOOM_CLIENT_ID`
- Copy **Client Secret** → `ZOOM_CLIENT_SECRET`
- Copy **Account ID** → `ZOOM_ACCOUNT_ID`

3. **Configure the bot endpoint and get Robot JID** — from **Surface** → **Team Chat Subscription**
- Enable **Team Chat Subscription**
- Set **Bot Endpoint URL** to `https://your-domain.com/api/webhooks/zoom`
- Copy the **Bot JID** that appears → `ZOOM_ROBOT_JID`

4. **Get the webhook secret** — from **Features** → **Access** → **Token**
- Copy the **Secret Token** → `ZOOM_WEBHOOK_SECRET_TOKEN`

5. **Add event subscriptions** — from **Features** → **Access** → **Event Subscription**
- Enable Event Subscription
- Set webhook URL to `https://your-domain.com/api/webhooks/zoom`
- Subscribe to: `bot_notification`, `team_chat.app_mention`

6. **Add scopes** — from **Scopes** (see [Required scopes](#required-scopes) below)

7. **Authorize the app** — from **Local Test** → **Add app now**
- Complete the OAuth flow to install the bot in your account

### Environment variables checklist

```bash
ZOOM_CLIENT_ID= # Basic Information → App Credentials
ZOOM_CLIENT_SECRET= # Basic Information → App Credentials
ZOOM_ACCOUNT_ID= # Basic Information → App Credentials
ZOOM_ROBOT_JID= # Surface → Team Chat Subscription → Bot JID
ZOOM_WEBHOOK_SECRET_TOKEN= # Features → Access → Token → Secret Token
```

### Local testing with ngrok

```bash
ngrok http 3000
# Use the https:// forwarding URL as your Bot Endpoint URL and event subscription URL
```

## Required scopes

| Scope | Purpose |
|-------|---------|
| `imchat:bot` | Send bot messages to channels and DMs |
| `team_chat:read:app_mention:admin` | Receive app_mention events |
| `team_chat:write:message:admin` | Send, edit, and delete messages |

## Webhook setup

```typescript
// app/api/webhooks/zoom/route.ts
import { bot } from "@/lib/bot";
import { after } from "next/server";

export async function POST(request: Request) {
return bot.adapters.zoom.handleWebhook(request, { waitUntil: after });
}
```

## Environment variables

| Variable | Required | Description |
|----------|----------|-------------|
| `ZOOM_CLIENT_ID` | Yes | OAuth app Client ID |
| `ZOOM_CLIENT_SECRET` | Yes | OAuth app Client Secret |
| `ZOOM_ACCOUNT_ID` | Yes | Zoom account ID (account-level app) |
| `ZOOM_ROBOT_JID` | Yes | Bot's JID from Marketplace app settings |
| `ZOOM_WEBHOOK_SECRET_TOKEN` | Yes | Webhook Secret Token from Event Subscriptions |
| `ZOOM_BOT_USERNAME` | No | Bot username for self-message detection (defaults to `zoom-bot`) |

## Configuration

| Option | Required | Description |
|--------|----------|-------------|
| `clientId` | No* | OAuth app Client ID. Auto-detected from `ZOOM_CLIENT_ID` |
| `clientSecret` | No* | OAuth app Client Secret. Auto-detected from `ZOOM_CLIENT_SECRET` |
| `accountId` | No* | Zoom account ID. Auto-detected from `ZOOM_ACCOUNT_ID` |
| `robotJid` | No* | Bot's JID. Auto-detected from `ZOOM_ROBOT_JID` |
| `webhookSecretToken` | No* | Webhook secret token. Auto-detected from `ZOOM_WEBHOOK_SECRET_TOKEN` |
| `userName` | No | Bot username. Auto-detected from `ZOOM_BOT_USERNAME` (defaults to `zoom-bot`) |
| `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |

*Required at runtime — either via config or environment variable.

## Features

### Messaging

| Feature | Supported |
|---------|-----------|
| Post message | Yes |
| Edit message | Yes |
| Delete message | Yes |
| Reply in thread | Yes |
| Streaming | Buffered (accumulates then sends) |

### Conversations

| Feature | Supported |
|---------|-----------|
| DMs | Yes (via bot_notification) |
| Channel slash commands | Yes (via bot_notification) |
| Thread subscription (subscribe flow) | No (Zoom platform limitation — see Known Limitations) |
| DM thread replies | No (Zoom platform limitation — see Known Limitations) |
| Reactions | No (not implemented in v1) |
| Typing indicator | No (Zoom has no typing indicator API) |

## Thread ID format

```
zoom:{channelId}:{messageId}
```

Examples:
- Channel message: `zoom:abc123@conference.xmpp.zoom.us:msg-id-456`
- DM: `zoom:{userJid}:{event_ts}` (sender-based channel with event timestamp as message ID)

## Known limitations

**Thread subscription (subscribe flow):** The `thread.subscribe()` pattern does not work for Zoom. Each `bot_notification` arrives with a unique thread ID based on `event_ts` — Zoom provides no `reply_to` or `parent_message_id` field to link replies to the original message. This is a confirmed Zoom platform limitation (verified against `@zoom/rivet` SDK, which also has no thread reply handler). Subsequent replies in a thread cannot be detected as part of the same conversation.

**DM thread replies (THRD-03):** Zoom does not fire `chat_message.replied` for 1:1 DM thread replies. The adapter cannot subscribe to or receive threaded replies in DMs.

**Unicode HMAC verification bug (ZOOM-506645):** Zoom's servers may normalize Unicode characters (emoji, accented characters) differently before computing the HMAC. Payloads containing non-ASCII characters may fail signature verification. The adapter logs the raw body hex on verification failure to aid diagnosis. If this affects you, contact Zoom Support referencing ZOOM-506645.

## Troubleshooting

**Webhook signature verification fails**
- Ensure you're reading the raw request body before any JSON parsing
- Emoji or non-ASCII characters in payloads may fail HMAC verification due to a Zoom server-side Unicode normalization issue (ZOOM-506645) — check debug logs for raw body hex

**Bot not receiving events**
- Confirm the Bot Endpoint URL in **Surface → Team Chat Subscription** matches your deployment URL
- Confirm the event subscription URL in **Features → Access → Event Subscription** matches too
- Verify the app is installed via **Local Test → Add app now** (OAuth flow must complete)

**Bot appears in Zoom but doesn't respond**
- Check that `ZOOM_WEBHOOK_SECRET_TOKEN` matches the Secret Token in **Features → Access → Token**
- Ensure the app is marked as **Admin-managed** in Basic Information

**DM thread replies not received**
- This is a confirmed Zoom platform limitation — `chat_message.replied` is not fired for 1:1 DM thread replies. See [Known limitations](#known-limitations).

**`postMessage` returns 401**
- The `/v2/im/chat/messages` endpoint requires a `client_credentials` token. Ensure you're not using `account_credentials` grant type.

## License

MIT
59 changes: 59 additions & 0 deletions packages/adapter-zoom/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "@chat-adapter/zoom",
"version": "0.1.0",
"description": "Zoom Team Chat adapter for chat-sdk",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest run --coverage",
"test:watch": "vitest",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"@chat-adapter/shared": "workspace:*",
"chat": "workspace:*"
},
"devDependencies": {
"@types/mdast": "^4.0.4",
"@types/node": "^25.3.2",
"@vitest/coverage-v8": "^4.0.18",
"tsup": "^8.3.5",
"typescript": "^5.7.2",
"vitest": "^4.0.18"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vercel/chat.git",
"directory": "packages/adapter-zoom"
},
"homepage": "https://github.com/vercel/chat#readme",
"bugs": {
"url": "https://github.com/vercel/chat/issues"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"chat",
"zoom",
"bot",
"adapter",
"messaging",
"team-chat"
],
"license": "MIT"
}
Loading