Generated banner for Build Zoom Team Chat App
Generated logo for Build Zoom Team Chat App

Build Zoom Team Chat App

AnthropicOfficialCreated by ZoomDistributed by Anthropic

  • Skill
  • Tool integration
  • Automation and integrations
  • MIT
  • Medium risk

From the author

Reference skill for Zoom Team Chat. Use after routing to a chat workflow when building user-scoped messaging integrations, chatbot experiences, rich cards, buttons, slash commands, or chat webhooks.
— the description by Zoom

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured framework for developing integrations with Zoom Team Chat. It clarifies the critical distinction between the Team Chat API (for user-authenticated messaging) and the Chatbot API (for interactive bot experiences). The documentation covers environment setup, authentication flows, webhook management, and the implementation of rich message cards, slash commands, and interactive UI components.

What it does

  • Distinguishes between user-level Team Chat API and bot-level Chatbot API implementations.
  • Provides setup guides for OAuth and Client Credentials authentication flows.
  • Details the structure and components for building rich, interactive message cards.
  • Explains how to handle webhook events for slash commands and button interactions.
  • Offers patterns for integrating LLMs and managing chatbot lifecycles.
  • Includes troubleshooting guides for common authentication and webhook issues.

When to use it

  • When you need to send automated notifications from CI/CD pipelines or scripts.
  • When building interactive chatbots that respond to user slash commands.
  • When creating rich UI elements like buttons, forms, or dropdowns within Zoom chat.
  • When you need to integrate external services or LLMs into a Zoom chat environment.

Advantages

  • Clear separation of concerns between user-based and bot-based integration types.
  • Comprehensive library of examples for common operations and interactive features.
  • Standardized approach to environment configuration and security best practices.
  • Centralized navigation for official documentation and sample applications.

Usage examples

“How do I send a simple message to a Zoom channel as a user?”
The skill provides the Team Chat API endpoint and a JavaScript fetch example using OAuth to post messages to a specific channel ID.
“I want to build a chatbot that uses buttons for user input.”
The skill guides you to use the Chatbot API, explains the required Client Credentials, and provides a template for sending rich message cards with action items.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/anthropics/build-zoom-team-chat-app/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip build-zoom-team-chat-app.zip -d .agents/skills

Documentation by Zoom

/build-zoom-team-chat-app

Background reference for Zoom Team Chat integrations. Use this after the workflow is clear, especially when the Team Chat API versus Chatbot API distinction matters.

Read This First (Critical)

There are two different integration types and they are not interchangeable:

  1. Team Chat API (user type)

    • Sends messages as a real authenticated user

    • Uses User OAuth (authorization_code)

    • Endpoint family: /v2/chat/users/...

  2. Chatbot API (bot type)

    • Sends messages as your bot identity

    • Uses Client Credentials (client_credentials)

    • Endpoint family: /v2/im/chat/messages

If you choose the wrong type early, auth/scopes/endpoints all mismatch and implementation fails.

Official Documentation: https://developers.zoom.us/docs/team-chat/
Chatbot Documentation: https://developers.zoom.us/docs/team-chat/chatbot/extend/
API Reference: https://developers.zoom.us/docs/api/rest/reference/chatbot/

New to Team Chat? Follow this path:

  1. Get Started - End-to-end fast path (user type vs bot type)

  2. Choose Your API - Team Chat API vs Chatbot API

  3. Environment Setup - Credentials, scopes, app configuration

  4. OAuth Setup - Complete authentication flow

  5. Send First Message - Working code to send messages

Reference:

Having issues?

OAuth endpoint sanity check:

  • Authorize URL: https://zoom.us/oauth/authorize

  • Token URL: https://zoom.us/oauth/token

  • If /oauth/token returns 404/HTML, use https://zoom.us/oauth/token.

Building Interactive Bots?

Quick Decision: Which API?

Use CaseAPI to Use
Send notifications from scripts/CI/CDTeam Chat API
Automate messages as a userTeam Chat API
Build an interactive chatbotChatbot API
Respond to slash commandsChatbot API
Create messages with buttons/formsChatbot API
Handle user interactionsChatbot API
Team Chat API (User-Level)
  • Messages appear as sent by authenticated user

  • Requires User OAuth (authorization_code flow)

  • Endpoint: POST https://api.zoom.us/v2/chat/users/me/messages

  • Scopes: chat_message:write, chat_channel:read

Chatbot API (Bot-Level)
  • Messages appear as sent by your bot

  • Requires Client Credentials grant

  • Endpoint: POST https://api.zoom.us/v2/im/chat/messages

  • Scopes: imchat:bot (auto-added)

  • Rich cards: buttons, forms, dropdowns, images

Prerequisites

System Requirements
  • Zoom account

  • Account owner, admin, or Zoom for developers role enabled

    • To enable: User Management → Roles → Role Settings → Advanced features → Enable Zoom for developers

Create Zoom App
  1. Go to Zoom App Marketplace

  2. Click Develop → Build App

  3. Select General App (OAuth)

⚠️ Do NOT use Server-to-Server OAuth - S2S apps don't have the Chatbot/Team Chat feature. Only General App (OAuth) supports chatbots.

Required Credentials

From Zoom Marketplace → Your App:

CredentialLocationUsed By
Client IDApp Credentials → DevelopmentBoth APIs
Client SecretApp Credentials → DevelopmentBoth APIs
Account IDApp Credentials → DevelopmentChatbot API
Bot JIDFeatures → Chatbot → Bot CredentialsChatbot API
Secret TokenFeatures → Team Chat SubscriptionsChatbot API

See: Environment Setup Guide for complete configuration steps.

Quick Start: Team Chat API

Send a message as a user:

// 1. Get access token via OAuth
const accessToken = await getOAuthToken(); // See examples/oauth-setup.md

// 2. Send message to channel
const response = await fetch('https://api.zoom.us/v2/chat/users/me/messages', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    message: 'Hello from CI/CD pipeline!',
    to_channel: 'CHANNEL_ID'
  })
});

const data = await response.json();
// { "id": "msg_abc123", "date_time": "2024-01-15T10:30:00Z" }

Complete example: Send Message Guide

Quick Start: Chatbot API

Build an interactive chatbot:

// 1. Get chatbot token (client_credentials)
async function getChatbotToken() {
  const credentials = Buffer.from(
    `${CLIENT_ID}:${CLIENT_SECRET}`
  ).toString('base64');
  
  const response = await fetch('https://zoom.us/oauth/token', {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${credentials}`,
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: 'grant_type=client_credentials'
  });
  
  return (await response.json()).access_token;
}

// 2. Send chatbot message with buttons
const response = await fetch('https://api.zoom.us/v2/im/chat/messages', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    robot_jid: process.env.ZOOM_BOT_JID,
    to_jid: payload.toJid,           // From webhook
    account_id: payload.accountId,   // From webhook
    content: {
      head: {
        text: 'Build Notification',
        sub_head: { text: 'CI/CD Pipeline' }
      },
      body: [
        { type: 'message', text: 'Deployment successful!' },
        {
          type: 'fields',
          items: [
            { key: 'Branch', value: 'main' },
            { key: 'Commit', value: 'abc123' }
          ]
        },
        {
          type: 'actions',
          items: [
            { text: 'View Logs', value: 'view_logs', style: 'Primary' },
            { text: 'Dismiss', value: 'dismiss', style: 'Default' }
          ]
        }
      ]
    }
  })
});

Complete example: Chatbot Setup Guide

Key Features

Team Chat API
FeatureDescription
Send MessagesPost messages to channels or direct messages
List ChannelsGet user's channels with metadata
Create ChannelsCreate public/private channels programmatically
Threaded RepliesReply to specific messages in threads
Edit/DeleteModify or remove messages
Chatbot API
FeatureDescription
Rich Message CardsHeaders, images, fields, buttons, forms
Slash CommandsCustom /commands trigger webhooks
Button ActionsInteractive buttons with webhook callbacks
Form SubmissionsCollect user input with forms
Dropdown SelectsChannel, member, date/time pickers
LLM IntegrationEasy integration with Claude, GPT, etc.

Webhook Events (Chatbot API)

EventTriggerUse Case
bot_notificationUser messages bot or uses slash commandProcess commands, integrate LLM
bot_installedBot added to accountInitialize bot state
interactive_message_actionsButton clickedHandle button actions
chat_message.submitForm submittedProcess form data
app_deauthorizedBot removedCleanup

See: Webhook Events Reference

Message Card Components

Build rich interactive messages with these components:

ComponentDescription
headerTitle and subtitle
messagePlain text
fieldsKey-value pairs
actionsButtons (Primary, Danger, Default styles)
sectionColored sidebar grouping
attachmentsImages with links
dividerHorizontal line
form_fieldText input
dropdownSelect menu
date_pickerDate selection

See: Message Cards Reference for complete component catalog

Architecture Patterns

Chatbot Lifecycle
User types /command → Webhook receives bot_notification
                            ↓
                     payload.cmd = "user's input"
                            ↓
                     Process command
                            ↓
                     Send response via sendChatbotMessage()
LLM Integration Pattern
case 'bot_notification': {
  const { toJid, cmd, accountId } = payload;
  
  // 1. Call your LLM
  const llmResponse = await callClaude(cmd);
  
  // 2. Send response back
  await sendChatbotMessage(toJid, accountId, {
    body: [{ type: 'message', text: llmResponse }]
  });
}

See: LLM Integration Guide

Sample Applications

SampleDescriptionLink
Chatbot QuickstartOfficial tutorial (recommended start)GitHub
Claude ChatbotAI chatbot with Anthropic ClaudeGitHub
Unsplash ChatbotImage search with databaseGitHub
ERP ChatbotOracle ERP with scheduled alertsGitHub
Task ManagerFull CRUD appGitHub

See: Sample Applications Guide for analysis of all 10 samples

Common Operations

Send Message to Channel
// Team Chat API
await fetch('https://api.zoom.us/v2/chat/users/me/messages', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` },
  body: JSON.stringify({
    message: 'Hello!',
    to_channel: 'CHANNEL_ID'
  })
});
Handle Button Click
// Webhook handler
case 'interactive_message_actions': {
  const { actionItem, toJid, accountId } = payload;
  
  if (actionItem.value === 'approve') {
    await sendChatbotMessage(toJid, accountId, {
      body: [{ type: 'message', text: '✅ Approved!' }]
    });
  }
}
Verify Webhook Signature
function verifyWebhook(req) {
  const message = `v0:${req.headers['x-zm-request-timestamp']}:${JSON.stringify(req.body)}`;
  const hash = crypto.createHmac('sha256', process.env.ZOOM_VERIFICATION_TOKEN)
    .update(message)
    .digest('hex');
  return req.headers['x-zm-signature'] === `v0=${hash}`;
}

Deployment

ngrok for Local Development
# Install ngrok
npm install -g ngrok

# Expose local server
ngrok http 4000

# Use HTTPS URL as Bot Endpoint URL in Zoom Marketplace
# Example: https://abc123.ngrok.io/webhook
Production Deployment

See: Deployment Guide for:

  • Nginx reverse proxy setup

  • Base path configuration

  • OAuth redirect URI setup

Limitations

LimitValue
Message length4,096 characters
File size512 MB
Members per channel10,000
Channels per user500

Security Best Practices

  1. Verify webhook signatures - Always validate using x-zm-signature header

  2. Sanitize messages - Limit to 4096 chars, remove control characters

  3. Validate JIDs - Check format: user@domain or channel@domain

  4. Environment variables - Never hardcode credentials

  5. Use HTTPS - Required for production webhooks

See: Security Best Practices

Complete Documentation Library

Core Concepts (Start Here!)
Complete Examples
References
Troubleshooting

Resources


Need help? Start with Integrated Index section below for complete navigation.


Integrated Index

This section was migrated from SKILL.md.

Complete navigation guide for the Zoom Team Chat skill.

Quick Start Paths

Path 1: Team Chat API (User-Level Messaging)

For sending messages as a user account.

  1. API Selection Guide - Confirm Team Chat API is right

  2. Environment Setup - Get credentials

  3. OAuth Setup Example - Implement authentication

  4. Send Message Example - Send your first message

Path 2: Chatbot API (Interactive Bots)

For building interactive chatbots with rich messages.

  1. API Selection Guide - Confirm Chatbot API is right

  2. Environment Setup - Get credentials (including Bot JID)

  3. Webhook Architecture - Understand webhook events

  4. Chatbot Setup Example - Build your first bot

  5. Message Cards Reference - Create rich messages

Core Concepts

Essential understanding for both APIs.

DocumentDescription
API Selection GuideChoose Team Chat API vs Chatbot API
Environment SetupComplete credentials and app configuration
Authentication FlowsOAuth vs Client Credentials
Webhook ArchitectureHow webhooks work (Chatbot API)
Message Card StructureCard component hierarchy
Deployment GuideProduction deployment strategies
Security Best PracticesSecure your integration

Complete Examples

Working code for common scenarios.

Authentication
ExampleDescription
OAuth SetupUser OAuth flow implementation
Token ManagementRefresh tokens, expiration handling
Basic Operations
ExampleDescription
Send MessageTeam Chat API message sending
Chatbot SetupComplete chatbot with webhooks
List ChannelsGet user's channels
Create ChannelCreate public/private channels
Interactive Features (Chatbot API)
ExampleDescription
Button ActionsHandle button clicks
Form SubmissionsProcess form data
Slash CommandsCreate custom commands
Dropdown SelectsChannel/member pickers
Advanced Integration
ExampleDescription
LLM IntegrationIntegrate Claude/GPT
Scheduled AlertsCron + incoming webhooks
Database IntegrationStore conversation state
Multi-Step WorkflowsComplex user interactions

References

API Documentation
ReferenceDescription
API ReferencePointers and common endpoints
Webhook EventsEvent types and handling checklist
Message CardsAll card components
Error CodesError handling guide
Sample Applications
ReferenceDescription
Sample ApplicationsSample app index/notes
Field Guides
ReferenceDescription
JID FormatsUnderstanding JID identifiers
Scopes ReferenceCommon scopes
Rate LimitsThrottling guidance

Troubleshooting

GuideDescription
Common IssuesQuick diagnostics and solutions
OAuth IssuesAuthentication failures
Webhook IssuesWebhook debugging
Message IssuesMessage sending problems
Deployment IssuesProduction problems

Architecture Patterns

Chatbot Lifecycle
User Action → Webhook → Process → Response
LLM Integration Pattern
User Input → Chatbot receives → Call LLM → Send response
Approval Workflow Pattern
Request → Send card with buttons → User clicks → Update status → Notify

Common Use Cases

Notifications
  • CI/CD build notifications

  • Server monitoring alerts

  • Scheduled reports

  • System health checks

Workflows
  • Approval requests

  • Task assignment

  • Status updates

  • Form submissions

Integrations
  • LLM-powered assistants

  • Database queries

  • External API integration

  • File/image sharing

Automation
  • Scheduled messages

  • Auto-responses

  • Data collection

  • Report generation

Official Documentation
Sample Code
Tools
Community

Documentation Status

✅ Complete
  • Main skill.md entry point

  • API Selection Guide

  • Environment Setup

  • Webhook Architecture

  • Chatbot Setup Example (complete working code)

  • Message Cards Reference

  • Common Issues Troubleshooting

📝 Pending (High Priority)
  • OAuth Setup Example

  • Send Message Example

  • Button Actions Example

  • LLM Integration Example

  • Webhook Events Reference

  • API Reference

  • Sample Applications Analysis

📋 Planned (Lower Priority)
  • Form Submissions Example

  • Channel Management Examples

  • Database Integration Example

  • Error Codes Reference

  • Rate Limits Guide

  • Deployment troubleshooting

Getting Started Checklist

For Team Chat API
For Chatbot API
  • [ ] Read API Selection Guide

  • [ ] Complete Environment Setup

  • [ ] Obtain Client ID, Client Secret, Bot JID, Secret Token, Account ID

  • [ ] Enable Team Chat in Features

  • [ ] Configure Bot Endpoint URL and Slash Command

  • [ ] Set up ngrok for local testing

  • [ ] Implement webhook handler

  • [ ] Send first chatbot message

Version History

  • v1.0 (2026-02-09) - Initial comprehensive documentation

    • Core concepts (API selection, environment setup, webhooks)

    • Complete chatbot setup example

    • Message cards reference

    • Common issues troubleshooting

Support

Use this SKILL.md as the navigation hub for Team Chat API selection, setup, examples, and troubleshooting.

Environment Variables

Files

concepts/webhooks.md

Webhook Architecture

Complete guide to understanding and implementing Zoom Team Chat webhooks for interactive chatbots.

Overview

Webhooks are HTTP POST requests that Zoom sends to your Bot Endpoint URL when specific events occur (slash commands, button clicks, form submissions, etc.).

How It Works
User action in Zoom → Zoom sends webhook → Your server processes → Send response

Example flow:

1. User types "/weather San Francisco" in Zoom Team Chat
2. Zoom sends POST request to your Bot Endpoint URL
3. Your server receives webhook with payload.cmd = "San Francisco"
4. Your server calls weather API
5. Your server sends chatbot message back with weather data

Webhook Lifecycle

Setup (One-time)
  1. Configure Bot Endpoint URL in Zoom Marketplace:

    • Development: https://abc123.ngrok.io/webhook

    • Production: https://yourdomain.com/webhook

  2. Verify endpoint - Zoom sends validation request when you save the URL

Runtime (Per Event)
User action → Zoom webhook → Your handler → Response

Webhook Events

EventTriggerWhen It Fires
endpoint.url_validationURL configured/changedSetup only
bot_installedBot added to accountInstallation
bot_notificationUser messages bot or uses slash commandUser interaction
interactive_message_actionsButton clickedUser clicks button
chat_message.submitForm submittedUser submits form
app_deauthorizedBot removed from accountUninstallation

See: Webhook Events Reference for complete event catalog

Webhook Structure

Request Headers

Every webhook includes these headers:

{
  'x-zm-signature': 'v0=abc123...',          // Signature for verification
  'x-zm-request-timestamp': '1234567890',    // Unix timestamp
  'content-type': 'application/json'
}
Request Body
{
  "event": "bot_notification",  // Event type
  "payload": {                   // Event-specific data
    "accountId": "...",
    "toJid": "...",
    "cmd": "...",
    // ... more fields
  }
}

Webhook Verification

CRITICAL: Always verify webhook signatures to prevent unauthorized requests.

Why Verify?

Without verification, anyone can send fake webhooks to your endpoint, potentially:

  • Triggering unauthorized actions

  • Causing denial-of-service attacks

  • Accessing sensitive data

Verification Algorithm
const crypto = require('crypto');

function verifyZoomWebhookSignature(req) {
  const signature = req.headers['x-zm-signature'];
  const timestamp = req.headers['x-zm-request-timestamp'];
  const secretToken = process.env.ZOOM_VERIFICATION_TOKEN;

  if (!signature || !timestamp) {
    throw new Error('Missing signature headers');
  }

  // Construct message
  const message = `v0:${timestamp}:${JSON.stringify(req.body)}`;

  // Calculate expected signature
  const expectedSignature = crypto
    .createHmac('sha256', secretToken)
    .update(message)
    .digest('hex');

  // Compare signatures
  if (signature !== `v0=${expectedSignature}`) {
    throw new Error('Invalid webhook signature');
  }

  return true;
}
Verification Flow
1. Extract signature and timestamp from headers
2. Construct message: "v0:{timestamp}:{JSON body}"
3. Calculate HMAC-SHA256 with secret token
4. Compare calculated signature with header signature
5. Accept if match, reject if mismatch

Webhook Handler Pattern

Basic Handler
app.post('/webhook', (req, res) => {
  try {
    // Step 1: Verify signature
    verifyZoomWebhookSignature(req);

    // Step 2: Extract event and payload
    const { event, payload } = req.body;

    // Step 3: Handle event
    switch (event) {
      case 'endpoint.url_validation':
        return handleUrlValidation(req, res);
      
      case 'bot_installed':
        return handleBotInstalled(payload, res);
      
      case 'bot_notification':
        return handleBotNotification(payload, res);
      
      case 'interactive_message_actions':
        return handleButtonClick(payload, res);
      
      case 'app_deauthorized':
        return handleBotUninstalled(payload, res);
      
      default:
        console.log('Unsupported event:', event);
        return res.status(200).json({ success: true });
    }
  } catch (error) {
    if (error.message.includes('signature')) {
      return res.status(401).json({ error: 'Invalid webhook signature' });
    }
    return res.status(500).json({ error: error.message });
  }
});

Event Handlers

1. URL Validation (endpoint.url_validation)

Zoom sends this when you configure or change your Bot Endpoint URL.

Purpose: Verify you control the endpoint

Payload:

{
  "event": "endpoint.url_validation",
  "payload": {
    "plainToken": "xyz123abc"
  }
}

Required Response:

{
  "plainToken": "xyz123abc",
  "encryptedToken": "hmac_sha256(plainToken, secret_token)"
}

Implementation:

function handleUrlValidation(req, res) {
  const { plainToken } = req.body.payload;
  
  const encryptedToken = crypto
    .createHmac('sha256', process.env.ZOOM_VERIFICATION_TOKEN)
    .update(plainToken)
    .digest('hex');

  return res.status(200).json({
    plainToken,
    encryptedToken
  });
}
2. Bot Installed (bot_installed)

Fired when someone adds your bot to their account.

Payload:

{
  "event": "bot_installed",
  "payload": {
    "accountId": "...",
    "userId": "...",
    "timestamp": 1234567890
  }
}

Use Case: Initialize bot state, send welcome message

Implementation:

async function handleBotInstalled(payload, res) {
  console.log('Bot installed for account:', payload.accountId);
  
  // Optional: Initialize database, send welcome message
  // await initializeBotForAccount(payload.accountId);
  
  return res.status(200).json({ success: true });
}
3. Bot Notification (bot_notification)

Fired when:

  • User sends message to bot via slash command

  • User sends direct message to bot

Payload:

{
  "event": "bot_notification",
  "payload": {
    "accountId": "...",
    "toJid": "[email protected]",
    "robotJid": "[email protected]",
    "userJid": "[email protected]",
    "cmd": "user's input text",
    "userName": "John Doe",
    "channelName": "Marketing",
    "timestamp": 1234567890
  }
}

Key Fields:

  • cmd - User's input after the slash command

  • toJid - Where to send response (channel or DM)

  • accountId - Account identifier

Use Case: Process commands, integrate LLM, send responses

Implementation:

async function handleBotNotification(payload, res) {
  const { toJid, cmd, accountId, userName } = payload;
  
  console.log(`${userName} sent: ${cmd}`);
  
  // Process command (e.g., call LLM)
  const response = await processCommand(cmd);
  
  // Send response
  await sendChatbotMessage(toJid, accountId, {
    body: [{ type: 'message', text: response }]
  });
  
  return res.status(200).json({ success: true });
}
4. Interactive Message Actions (interactive_message_actions)

Fired when user clicks a button in a chatbot message.

Payload:

{
  "event": "interactive_message_actions",
  "payload": {
    "accountId": "...",
    "toJid": "...",
    "actionItem": {
      "text": "Approve",
      "value": "approve"  // This is what you check
    },
    "messageId": "...",
    "userName": "John Doe"
  }
}

Key Field: actionItem.value - The button's value you defined

Implementation:

async function handleButtonClick(payload, res) {
  const { actionItem, toJid, accountId, userName } = payload;
  
  console.log(`${userName} clicked: ${actionItem.value}`);
  
  switch (actionItem.value) {
    case 'approve':
      await sendChatbotMessage(toJid, accountId, {
        body: [{ type: 'message', text: '✅ Approved!' }]
      });
      break;
    
    case 'reject':
      await sendChatbotMessage(toJid, accountId, {
        body: [{ type: 'message', text: '❌ Rejected' }]
      });
      break;
    
    default:
      console.log('Unknown action:', actionItem.value);
  }
  
  return res.status(200).json({ success: true });
}

Webhook Best Practices

1. Always Verify Signatures
// ✅ GOOD
app.post('/webhook', (req, res) => {
  verifyZoomWebhookSignature(req);
  // ... handle event
});

// ❌ BAD
app.post('/webhook', (req, res) => {
  // No verification - vulnerable to fake webhooks!
});
2. Respond Quickly

Zoom expects a 200 response within 3 seconds.

// ✅ GOOD - Respond immediately, process async
app.post('/webhook', (req, res) => {
  verifyZoomWebhookSignature(req);
  
  // Respond immediately
  res.status(200).json({ success: true });
  
  // Process asynchronously
  processWebhookAsync(req.body);
});

// ❌ BAD - Slow processing blocks response
app.post('/webhook', async (req, res) => {
  await slowLLMCall();  // May timeout!
  res.status(200).json({ success: true });
});
3. Handle All Events Gracefully
// ✅ GOOD - Handle unknown events
switch (event) {
  case 'bot_notification':
    return handleBotNotification(payload, res);
  default:
    console.log('Unsupported event:', event);
    return res.status(200).json({ success: true });
}

// ❌ BAD - Crash on unknown events
switch (event) {
  case 'bot_notification':
    return handleBotNotification(payload, res);
  // Missing default case - crashes on new events!
}
4. Log Webhook Activity
app.post('/webhook', (req, res) => {
  const { event, payload } = req.body;
  
  console.log(`[Webhook] ${event}`, {
    timestamp: new Date().toISOString(),
    accountId: payload.accountId,
    userId: payload.userId
  });
  
  // ... handle event
});
5. Use Environment Variables
// ✅ GOOD
const SECRET_TOKEN = process.env.ZOOM_VERIFICATION_TOKEN;

// ❌ BAD - Hardcoded secret
const SECRET_TOKEN = 'abc123xyz';

Testing Webhooks

Local Development with ngrok
# Install ngrok
npm install -g ngrok

# Expose local server
ngrok http 4000

# Copy HTTPS URL to Zoom Marketplace
# Example: https://abc123.ngrok.io/webhook
Manual Testing
WEBHOOK_BASE_URL="http://YOUR_DEV_HOST:4000"

# Test with curl (will fail signature verification - expected)
curl -X POST "$WEBHOOK_BASE_URL/webhook" \
  -H "Content-Type: application/json" \
  -d '{"event":"test"}'

# Expected response: "Invalid webhook signature" (this is correct!)
Verify Webhook is Working

Success indicators:

  1. Zoom successfully validates your endpoint URL

  2. bot_installed event fires when you add the bot

  3. bot_notification fires when you use slash command

  4. Button clicks trigger interactive_message_actions

Common Webhook Issues

IssueCauseSolution
"Cannot GET /webhook"Browser sends GET, webhook is POSTNormal - test with POST or Zoom
"Invalid signature"Wrong secret tokenVerify ZOOM_VERIFICATION_TOKEN matches Zoom Marketplace
URL validation failsResponse format incorrectReturn plainToken + encryptedToken
No webhooks receivedWrong endpoint URLVerify URL in Zoom Marketplace matches your server
Webhooks timeoutSlow responseReturn 200 immediately, process async

Next Steps

Resources

Requirements and compatibility

Written for: Claude Code, Claude apps

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs network access, Needs Node.js

Security

What our scanner noticed· Medium risk

  • Possible data-exfiltration endpoints
  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Zoom
Publisher
Anthropic (Official)
Distributed by
Anthropic
Source
anthropics/knowledge-work-plugins · partner-built/zoom-plugin/skills/team-chat · commit e050c2e
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from partner-built/zoom-plugin/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Zoom or Anthropic.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download build-zoom-team-chat-app.zip