Troubleshooting
Organized by symptom: what you see → why it happens → how to fix it.
Bot starts but receives no updates
409 Conflict: terminated by other getUpdates request— two processes are long-polling the same token (a secondbot.start(), an old container still running, or polling while a webhook is set). Run exactly one instance. If you previously set a webhook, callbot.api.deleteWebhook()before polling.- A webhook is set, so polling gets nothing. Webhook and long-polling are mutually exclusive. Call
deleteWebhookto go back to polling, or keep the webhook and don't call plainbot.start(). - Opt-in updates never arrive (
chat_member,message_reaction,chat_join_request, business updates). Telegram excludes these from the defaultallowed_updates. Pass them explicitly:tsSee Updates.bot.start({ allowedUpdates: ["message", "chat_member", "message_reaction"] }); - In groups the bot only sees commands and @mentions. Privacy mode is on by default. Disable it via
/setprivacyin @BotFather — only if the bot must read all group messages.
401 / 404 from the API
401 Unauthorized— bad or empty token.new Bot(process.env.BOT_TOKEN as string)silently becomesundefinedif the env var is missing. Make sure the token is loaded (e.g.node --env-file=.env, dotenv, or your host's secrets) before constructing the bot.404 Not Foundon every method — usually a wrong API base URL (custom / local Bot API typo) or a token with a stray space or newline.
Formatting is broken (literal tags, no bold, double escaping)
These are the most common GramIO mistakes — full rules in Formatting.
- You see literal
<b>/*/ backslashes in the message. You passedparse_modetogether with aformattemplate. Never combine them —formatalready produces real entities. Dropparse_modeentirely. - Entities vanish when joining an array of formattables. Native
Array.prototype.join()stringifies and strips entities. Use thejoinhelper fromgramioinstead. - Entities vanish when reusing a
FormattableString. Plain template interpolation (`${myFormattable}`) strips entities; never call.toString()on it either. Always wrap reused formattables in an outerformat`...`. - Caption formatting ignored on media. Pass the
formatvalue as thecaption— and again, noparse_mode.
Callback buttons feel broken / spinner hangs
- An inline button shows a loading spinner for ~15s. The handler never called
answerCallbackQuery. Makeawait ctx.answer()the first line of everycallbackQueryhandler (an empty answer is fine), or install@gramio/auto-answer-callback-query. See UX Patterns §7. - The
callbackQueryhandler never fires. The button'scallback_datadoesn't match the handler's matcher. Prefer a typedCallbackDataschema and pass the same instance to both.pack()andbot.callbackQuery(schema, …). See Inline Keyboard. BUTTON_DATA_INVALID.callback_dataexceeds 64 bytes. Shorten the schema / pack fewer fields.
Scenes / multi-step flows
ctx.sceneis undefined / scenes do nothing.scenes()requiressession()installed first:.extend(session()).extend(scenes([...])). See Scenes.- A flow silently resets after a deploy or restart. You used
@gramio/prompt(in-memory) for a multi-step flow. The awaited promise dies with the process. Use Scenes.ask()(persists step + answers via storage) for anything that must survive restarts.
Context access
ctx.payload/ snake_case fields areundefinedor untyped. Don't read the raw payload. Every Telegram field is a camelCase getter on the context:ctx.from.firstName,ctx.chatId,ctx.messageId.- A getter you expected is
undefined. It's only present on the relevant update kind — narrow first withctx.is("message")or a filter, then access.
File uploads
sendPhotothrows or sends the literal path string.MediaUpload.path/url/bufferis async — you mustawaitit:tsAn already-uploadedawait ctx.sendPhoto(await MediaUpload.path("./p.jpg"));file_idis passed directly (noMediaUpload). See Files.
Webhook doesn't fire
- Telegram never hits your endpoint.
bot.start({ webhook: { url } })callssetWebhookbut does not start an HTTP server — you must mountwebhookHandler(bot, "<framework>")yourself and expose it over HTTPS. Verify withbot.api.getWebhookInfo()(checklast_error_message). See Webhook. - Local dev: Telegram can't reach
localhost. Use a tunnel (cloudflared / ngrok) and set the webhook to the public HTTPS URL.
Mini App (TMA) auth fails
initDatavalidation fails. Clock skew, the wrong bot token in the validator, or you're validating the already-parsed object instead of the rawinitDatastring. Validate the raw string with the correct token. See Mini Apps.
Types / build
- A custom
ctx.fooisanyor errors. Don't augment withdeclare module. Add it via.derive(ctx => ({ foo }))(per-update) or.decorate({ foo })(static) so the type flows automatically.
Still stuck?
- Search the Telegram Bot API method reference for the exact method and its error table.
- Ask in the GramIO chat.