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

Troubleshooting

The failure modes that actually come up, and what each one means.

Something read the request body before the adapter did. A Web Request body can only be read once and the signature covers the exact bytes Zoom sent, so a logger or middleware calling request.json() breaks verification for every event. Pass the untouched Request.

If the body is clean, check that ZOOM_WEBHOOK_SECRET_TOKEN matches the Secret Token under Features → Access → Token, and that your server’s clock is right — signatures older than five minutes are refused.

That is ZOOM-506645, a normalisation bug on Zoom’s side. Turn on debug logging: the adapter writes the first 200 bytes of the body as hex when the comparison fails, which is what Zoom Support needs. See Limitations.

Check all three in the Marketplace app:

  • Surface → Team Chat Subscription — the Bot Endpoint URL matches your deployment.
  • Features → Access → Event Subscription — the webhook URL matches too, and bot_notification and team_chat.app_mention are both subscribed.
  • Local Test → Add app now — the app is actually installed in the account.

With ngrok, a restart means a new URL and both fields need it again.

The events are probably arriving and failing verification — check the signature first. Otherwise confirm the app is Admin-managed, and that the process handling the webhook is the one holding your credentials.

ZoomAdapter: postMessage failed with HTTP 401 almost always means the wrong OAuth grant. The /v2/im/chat/messages endpoints need a client_credentials token, which is what the adapter requests; an account_credentials token is rejected. If you are supplying credentials yourself, check they belong to the Server-to-Server OAuth app you registered the bot under.

Confirmed Zoom behaviour, not a configuration problem: no event fires for replies inside a one-to-one thread. See Limitations.

The package is ESM only. Use import, and make sure the importing package is ESM too ("type": "module", or a .mjs file).

Check that Markdown mode is on: markdownSupport defaults to true, so only an explicit false (or ZOOM_MARKDOWN_SUPPORT=false) turns it off. If you develop against a local copy of the package, rebuild it — a file: dependency is a snapshot, so pnpm build alone does not update it. A link: dependency or a reinstall picks up the new dist/.

Also note that Zoom’s chatbot parser uses *bold*, not **bold**, for bold. The adapter already sends the right token; if you are constructing raw payloads yourself, **text** renders as plain text. Formatting has the verified token table.

Expected. Zoom’s Markdown parser has no underline token and no table token. The words and cells are still sent as plain text. See Formatting.

Was this page helpful?