---
title: Troubleshooting
description: 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](/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](/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](/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](/formatting).
