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

Build Zoom Team Chat App

OpenAIOfficialCreated by ZoomDistributed by OpenAI

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Medium risk

From the author

Use when building Team Chat.
— the description by Zoom

AutoGineer summary

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

This skill provides a systematic approach to building applications for Zoom Team Chat. It guides developers through selecting the appropriate API surface, configuring app settings, modeling message structures, and implementing secure authentication and webhook handling. It covers the lifecycle of building both user-scoped messaging integrations and interactive chatbot workflows.

What it does

  • Helps select between Team Chat API and Chatbot API based on integration requirements.
  • Outlines the configuration of app types, scopes, and role enablement.
  • Provides guidance on modeling message structures including rich cards, forms, and slash commands.
  • Details the implementation of OAuth flows and token management.
  • Supports the setup of webhook handlers with signature verification.
  • Assists in debugging common issues related to JID formats, channel membership, and payload shapes.

When to use it

  • When you need to integrate custom messaging or automation into Zoom Team Chat.
  • When designing interactive chatbot workflows with buttons, forms, or dropdowns.
  • When setting up OAuth authentication for Zoom-based applications.
  • When troubleshooting connectivity or message delivery issues in a Zoom integration.

Advantages

  • Provides a clear, step-by-step path for Zoom app development.
  • Ensures security best practices like signature verification are prioritized.
  • Helps avoid common pitfalls in API selection and message formatting.
  • Centralizes references for environment variables and API endpoints.

Usage examples

“How do I start building a chatbot for Zoom Team Chat?”
The agent will guide you through choosing the Chatbot API, configuring the bot identity, and setting up the necessary OAuth and webhook infrastructure.
“I am getting an invalid access token error when sending messages.”
The agent will help you verify your app type, check if the required scopes are enabled, and confirm if the user has re-consented after recent scope changes.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/openai/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

Use this skill when the target surface is Zoom Team Chat. First decide whether the integration is user-scoped messaging, a chatbot, an interactive message-card workflow, or a webhook-driven automation.

Workflow

  1. Choose the API surface: Team Chat API for user-scoped actions, Chatbot API for bot identity and chatbot workflows.

  2. Confirm app type, scopes, role enablement, and whether the account has Zoom for Developers enabled.

  3. Model message structure before coding: plain messages, rich cards, buttons, dropdowns, forms, slash commands, and threaded replies.

  4. Implement OAuth and token refresh separately from message sending, with clear storage boundaries.

  5. Add webhook handlers for interactivity and lifecycle events with signature verification and retry-safe processing.

  6. Debug by checking JID formats, channel membership, bot installation, scopes, role settings, and message-card payload shape.

References

Files

references/samples.md

Sample Applications Analysis

Analysis of official Zoom Team Chat sample applications, extracted patterns, and best practices.

Sample Overview

SampleLanguageComplexityBest For
chatbot-nodejs-quickstartNode.js⭐ BeginnerStart here - Tutorial series
LLM chatbot patternNode.js⭐⭐ IntermediateProvider-neutral LLM integration pattern
unsplash-chatbotNode.js⭐⭐ IntermediateAPI integration + database
zoom-erp-chatbot-sampleNode.js⭐⭐⭐ AdvancedEnterprise integration
task-manager-sampleNode.js⭐⭐⭐ AdvancedFull CRUD application
zoom-cohere-chatbot-sampleNode.js⭐⭐ IntermediateCohere LLM integration
zoom-cerebras-chatbot-sampleNode.js⭐⭐ IntermediateCerebras LLM integration
zoom-team-chat-shortcut-sampleNode.js⭐⭐ IntermediateShortcuts and UI elements
zoom-teams-chat-snowflake-sampleNode.js⭐⭐⭐ AdvancedSnowflake data integration
rivet-javascript-sampleNode.js⭐⭐ IntermediateRivet SDK usage

1. chatbot-nodejs-quickstart

Repository: https://github.com/zoom/chatbot-nodejs-quickstart

Description: Official tutorial series covering 9 episodes from setup to advanced features.

Key Features:

  • Setup & Send Messages

  • Handle Events

  • Slash Commands

  • Markdown & Emojis

  • Reactions & Interactive Messages

  • Threaded Replies

  • Search Messages via API

  • Scheduling Messages

  • Zoom Workplace App Integration

Project Structure:

chatbot-nodejs-quickstart/
├── routes/
│   ├── zoom-webhookHandler.js     # Webhook event handling
│   └── oauth-routes.js             # OAuth flow
├── utils/
│   ├── zoom-api.js                 # API helper functions
│   ├── zoom-chatbot-auth.js        # Token generation
│   └── validation.js               # Webhook signature verification
├── views/                          # EJS templates
├── server.js                       # Express app
└── .env.example                    # Environment variables

Key Patterns:

Webhook Handler Pattern
async function handleZoomWebhook(req, res) {
  verifyZoomWebhookSignature(req);

  const { event, payload } = req.body;

  switch (event) {
    case 'bot_notification':
      return handleBotNotification(payload, res);
    case 'interactive_message_actions':
      return handleButtonClick(payload, res);
    // ... more cases
  }
}
Token Generation
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}` },
    body: 'grant_type=client_credentials'
  });

  return (await response.json()).access_token;
}

Best Practices:

  • ✅ Signature verification on all webhooks

  • ✅ Environment variables for credentials

  • ✅ Modular route structure

  • ✅ Error handling with try/catch

  • ✅ Immediate webhook response (200 status)

Recommended For: First-time chatbot developers

2. LLM chatbot pattern

Description: AI-powered chatbot using an LLM provider for natural language responses.

Key Features:

  • LLM API integration

  • Conversation history tracking

  • Streaming responses (optional)

  • Context management

LLM Integration Pattern:

case 'bot_notification': {
  const { toJid, cmd, accountId } = payload;

  // Call your LLM provider
  const response = await llmClient.responses.create({
    model: process.env.LLM_MODEL,
    max_tokens: 1024,
    messages: [{ role: 'user', content: cmd }]
  });

  const llmResponse = response.content[0].text;

  // Send back to Zoom
  await sendChatbotMessage(toJid, accountId, {
    body: [{ type: 'message', text: llmResponse }]
  });
}

Conversation History Pattern:

const conversationHistory = new Map();

function addToHistory(userId, role, content) {
  if (!conversationHistory.has(userId)) {
    conversationHistory.set(userId, []);
  }
  conversationHistory.get(userId).push({ role, content });
}

// In bot_notification handler
const history = conversationHistory.get(userId) || [];
const response = await llmClient.responses.create({
  model: process.env.LLM_MODEL,
  messages: history
});

Environment Variables:

LLM_API_KEY=your_api_key_here
LLM_MODEL=your_model_here
ZOOM_CLIENT_ID=...
ZOOM_CLIENT_SECRET=...
ZOOM_BOT_JID=...

Recommended For: Building AI assistants

3. unsplash-chatbot

Repository: https://github.com/zoom/unsplash-chatbot

Description: Image search bot integrating Unsplash API with database storage.

Key Features:

  • Third-party API integration (Unsplash)

  • Database persistence (SQLite/PostgreSQL)

  • Image search and display

  • User preference storage

Database Schema:

CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  zoom_user_id TEXT UNIQUE,
  preferences TEXT
);

CREATE TABLE searches (
  id INTEGER PRIMARY KEY,
  user_id INTEGER,
  query TEXT,
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

Image Display Pattern:

{
  "content": {
    "head": { "text": "Image Results" },
    "body": [
      {
        "type": "attachments",
        "img_url": imageData.urls.regular,
        "resource_url": imageData.links.html,
        "information": {
          "title": { "text": imageData.description },
          "description": { "text": `Photo by ${imageData.user.name}` }
        }
      }
    ]
  }
}

Best Practices:

  • ✅ API rate limiting handling

  • ✅ Error handling for external APIs

  • ✅ Database connection pooling

  • ✅ User data privacy

Recommended For: External API integration patterns

4. zoom-erp-chatbot-sample

Repository: https://github.com/zoom/zoom-erp-chatbot-sample

Description: Enterprise Resource Planning integration with scheduled alerts.

Key Features:

  • Oracle ERP API integration

  • Scheduled notifications (cron)

  • Approval workflows

  • Threaded conversations

Scheduled Alerts Pattern:

const cron = require('node-cron');

// Daily report at 9 AM
cron.schedule('0 9 * * *', async () => {
  const report = await getERPReport();

  await sendChatbotMessage(channelJid, accountId, {
    head: { "text": "Daily ERP Report" },
    body: [
      { "type": "fields", "items": report.fields },
      {
        "type": "actions",
        "items": [
          { "text": "View Details", "value": "view_report" }
        ]
      }
    ]
  });
});

Approval Workflow Pattern:

// Send approval request
{
  "head": { "text": "Expense Approval Required" },
  "body": [
    { "type": "fields", "items": expenseFields },
    {
      "type": "actions",
      "items": [
        { "text": "Approve", "value": `approve_${expenseId}`, "style": "Primary" },
        { "text": "Reject", "value": `reject_${expenseId}`, "style": "Danger" }
      ]
    }
  ]
}

// Handle button click
case 'interactive_message_actions': {
  const action = payload.actionItem.value;
  const [decision, expenseId] = action.split('_');

  await updateERPStatus(expenseId, decision);
  await sendConfirmation(payload.toJid, decision);
}

Recommended For: Enterprise integrations, workflows

5. task-manager-sample

Repository: https://github.com/zoom/task-manager-sample

Description: Full-featured task management application with CRUD operations.

Key Features:

  • Create, read, update, delete tasks

  • Task assignment

  • Due date tracking

  • Status management

  • Persistent storage

CRUD Pattern:

// CREATE
case 'bot_notification': {
  if (cmd.startsWith('create task')) {
    const taskData = parseTaskCommand(cmd);
    const task = await db.createTask(taskData);
    await sendTaskCreatedMessage(toJid, accountId, task);
  }
}

// READ
case 'interactive_message_actions': {
  if (actionItem.value.startsWith('view_task')) {
    const taskId = actionItem.value.split('_')[2];
    const task = await db.getTask(taskId);
    await sendTaskDetails(toJid, accountId, task);
  }
}

// UPDATE
case 'interactive_message_actions': {
  if (actionItem.value.startsWith('complete_task')) {
    const taskId = actionItem.value.split('_')[2];
    await db.updateTaskStatus(taskId, 'completed');
    await sendStatusUpdate(toJid, accountId, taskId);
  }
}

// DELETE
case 'interactive_message_actions': {
  if (actionItem.value.startsWith('delete_task')) {
    const taskId = actionItem.value.split('_')[2];
    await db.deleteTask(taskId);
    await sendDeletionConfirmation(toJid, accountId, taskId);
  }
}

Recommended For: Full application architecture

Common Patterns Across Samples

1. Environment Variable Management

All samples use .env files with similar structure:

# Authentication
ZOOM_CLIENT_ID=
ZOOM_CLIENT_SECRET=
ZOOM_BOT_JID=
ZOOM_VERIFICATION_TOKEN=
ZOOM_ACCOUNT_ID=

# Third-party APIs (if applicable)
LLM_API_KEY=
UNSPLASH_ACCESS_KEY=

# Server
PORT=4000
NODE_ENV=development
2. Project Structure

Common folder organization:

sample-app/
├── routes/
│   ├── webhook.js           # Webhook handlers
│   └── oauth.js             # OAuth flows (if needed)
├── utils/
│   ├── zoom-api.js          # Zoom API wrappers
│   ├── auth.js              # Token management
│   └── validation.js        # Input validation
├── models/                  # Database models (if applicable)
├── views/                   # Frontend templates (if applicable)
├── server.js                # Express app
├── .env.example
└── package.json
3. Webhook Verification

All samples verify webhook signatures:

function verifyWebhook(req) {
  const signature = req.headers['x-zm-signature'];
  const timestamp = req.headers['x-zm-request-timestamp'];
  const message = `v0:${timestamp}:${JSON.stringify(req.body)}`;

  const hash = crypto.createHmac('sha256', SECRET_TOKEN)
    .update(message)
    .digest('hex');

  return signature === `v0=${hash}`;
}
4. Error Handling

Consistent error handling pattern:

app.post('/webhook', async (req, res) => {
  try {
    verifyWebhook(req);
    await handleWebhook(req.body);
    res.status(200).json({ success: true });
  } catch (error) {
    console.error('Webhook error:', error);

    if (error.message.includes('signature')) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    res.status(500).json({ error: 'Internal server error' });
  }
});
5. Async Webhook Processing

Respond immediately, process async:

app.post('/webhook', (req, res) => {
  // Respond immediately
  res.status(200).json({ success: true });

  // Process asynchronously
  processWebhookAsync(req.body).catch(error => {
    console.error('Async processing error:', error);
  });
});

Architecture Lessons

Chatbot Lifecycle

Common lifecycle across all samples:

1. User Action (slash command, button click, message)
   ↓
2. Zoom sends webhook to Bot Endpoint URL
   ↓
3. Server verifies signature
   ↓
4. Server responds 200 (immediately)
   ↓
5. Server processes request (async)
   ↓
6. Server calls external APIs if needed
   ↓
7. Server sends chatbot message back to Zoom
State Management

Simple bots: In-memory state (Map/Object) Production bots: Database (PostgreSQL, MongoDB, Redis)

// Simple (development)
const userState = new Map();

// Production
const userState = {
  async get(userId) {
    return await db.query('SELECT * FROM user_state WHERE user_id = $1', [userId]);
  },
  async set(userId, state) {
    return await db.query('INSERT INTO user_state (user_id, state) VALUES ($1, $2) ON CONFLICT (user_id) DO UPDATE SET state = $2', [userId, state]);
  }
};

Deprecation Notes

Some samples may use deprecated patterns:

❌ Old Pattern (Don't Use)
// Hardcoded credentials
const CLIENT_ID = 'abc123';
✅ New Pattern (Use This)
// Environment variables
const CLIENT_ID = process.env.ZOOM_CLIENT_ID;
❌ Old Pattern (Don't Use)
// Synchronous webhook processing (may timeout)
app.post('/webhook', async (req, res) => {
  await longRunningProcess();
  res.status(200).json({ success: true });
});
✅ New Pattern (Use This)
// Async processing
app.post('/webhook', (req, res) => {
  res.status(200).json({ success: true });
  longRunningProcess().catch(console.error);
});

Sample Selection Guide

Choose chatbot-nodejs-quickstart if:
  • You're new to Zoom chatbots

  • You want a tutorial series

  • You need step-by-step guidance

Choose an LLM chatbot pattern if:
  • You want to integrate an LLM

  • You need conversational AI

  • You want to see LLM integration patterns

Choose unsplash-chatbot if:
  • You need to integrate external APIs

  • You want database patterns

  • You need user preference storage

Choose zoom-erp-chatbot-sample if:
  • You're building enterprise integrations

  • You need scheduled notifications

  • You want approval workflows

Choose task-manager-sample if:
  • You want a full CRUD application

  • You need complex state management

  • You want to see production architecture

Next Steps

Resources

Requirements and compatibility

Written for: OpenAI Codex

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.

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
OpenAI (Official)
Distributed by
OpenAI
Source
openai/plugins · plugins/zoom/skills/team-chat · commit d420d13
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from plugins/zoom/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

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

Download ZIP

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