Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Webhooks

The single Zoom endpoint — the setup challenge, signature checks, event dispatch, and answering Zoom quickly.

Zoom delivers everything a bot hears to one HTTP endpoint. Hand the request straight over:

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

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

At the endpoint

Zoom sends

POST /api/webhooks/zoom

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

The adapter answers

200 OK

{
  "plainToken": "qgg8vlvZRS6UYooatFL8Aw",
  "encryptedToken": "23a89b634c017..."
}

Zoom sends this when you save the endpoint URL. It carries no signature, so it is answered before any signature check — otherwise the Marketplace could never verify the endpoint.

Answer Zoom first, work after

waitUntil — Next.js after, or a Cloudflare ExecutionContext.waitUntil — hands your event processing to the runtime so the 200 goes back immediately. Without it the response waits for your handlers to finish, and a slow handler becomes a slow webhook.

What comes back

Situation Response
The body is not valid JSON 400 Invalid JSON
The setup challenge (endpoint.url_validation) 200 with { plainToken, encryptedToken }
Missing, stale, or mismatched signature 401 Invalid signature
Handled or unrecognised event 200 ok

Return the response as it is. An unrecognised event is answered 200 ok on purpose: Zoom retries anything else.

Events

Event What it is
endpoint.url_validation The challenge Zoom sends when you save the endpoint URL
bot_notification A DM, a slash command, or a message to the bot in a channel
team_chat.app_mention An @-mention of the bot in a channel

Both message events reach your onNewMention handlers: Zoom has no separate mention event for bot_notification, so the adapter marks those messages as mentions too and your handlers fire consistently with other platforms.

Signature checks

Each event carries x-zm-signature and x-zm-request-timestamp. The adapter recomputes v0:{timestamp}:{raw body} as HMAC-SHA256 with your webhook secret token and compares it in constant time, rejecting anything signed more than five minutes ago.

If verification fails, the adapter writes the first 200 bytes of the body as hex to the logger at debug level. That is also where to look for the Unicode signature bug described in Limitations.

Before the bot is ready

Events that arrive before Chat has initialized the adapter are logged and dropped rather than queued. In practice this only shows up if your endpoint is live before the bot is constructed.

Was this page helpful?