Skip to main content
Share
Guides

Telegram Bot API Error Codes: What Each One Means and How to Fix It

Every Telegram Bot API error belongs to one of five families - the 409 conflicts that silence a bot, the 400 Bad Request swarm, the 403 Forbiddens, flood control 429s, and token 401/404s. This guide maps each literal description to its exact fix.

Content & Engineering
Aug 20, 2026
14 min read
Last verified August 2026
telegram bot api error codestelegram bot error 409telegram error 400 bad requesttelegram bot 403 forbiddentelegram 429 too many requests
TL;DR

Every Telegram Bot API error belongs to one of five families - the 409 conflicts that silence a bot, the 400 Bad Request swarm, the 403 Forbiddens, flood control 429s, and token 401/404s. This guide maps each literal description to its exact fix.

Key Takeaways
  • Telegram's HTTP status codes are coarse - almost everything is a 400 - but the description string is precise and documented.
  • Read the description, not the code, and you will usually know the fix before you finish the sentence.Last verified: 2026-08-20 against the Telegram Bot API.
  • Error strings quoted below are the literal responses the API returns.A failed call always has the same shape:{ "ok": false, "error_code": 429, "description": "Too Many Requests: retry after 34", "parameters": {"retry_after": 34} }Three fields matter.
  • error_code mirrors the HTTP status.

How to read a Telegram Bot API error before you look it up

Telegram's HTTP status codes are coarse - almost everything is a 400 - but the description string is precise and documented. Read the description, not the code, and you will usually know the fix before you finish the sentence.

Last verified: 2026-08-20 against the Telegram Bot API. Error strings quoted below are the literal responses the API returns.

A failed call always has the same shape:

{
  "ok": false,
  "error_code": 429,
  "description": "Too Many Requests: retry after 34",
  "parameters": {"retry_after": 34}
}

Three fields matter. error_code mirrors the HTTP status. description is the real error - a stable, documented English string, usually prefixed with the status name (Bad Request:, Forbidden:, Conflict:). And the optional parameters object carries machine-readable follow-ups: retry_after (seconds to wait) and migrate_to_chat_id (the group became a supergroup; here is its new ID). Code that logs only the status code throws away everything useful.

The five families, by symptom:

CodeFamilyTypical symptom
409Update-delivery conflictsBot is completely or intermittently silent
400Bad Request - your payloadSpecific sends fail; the rest work
403Forbidden - the recipientSends to certain users or chats fail permanently
429Flood controlWorked in testing, drops messages at volume
401 / 404Token problemsEvery single call fails

This guide walks each family code-first. If your bot is silent and you do not have an error string yet, start instead with our symptom-first companion, Telegram bot not responding - and for any literal string, the Telegram error directory has a dedicated page per error.

409 Conflict: the errors that make a bot silent

Telegram permits exactly one consumer of updates per bot token - you either poll with getUpdates or receive pushes at a webhook, never both, and never two pollers. Both violations return 409, and both silence your bot while every other part of the system looks healthy.

{"ok":false,"error_code":409,"description":"Conflict: can't use getUpdates method while webhook is active; use deleteWebhook to delete the webhook first"}

A webhook is registered against the token and your code is polling. Many libraries swallow this error and retry forever, so the process runs, the logs look fine, and nothing ever arrives. Fix: deleteWebhook?drop_pending_updates=true, then restart polling - or stop polling if the webhook was intentional. Full handling on the webhook-active 409 page.

{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}

Two processes are polling the same token, and each message goes to whichever holds the connection - the classic bot that answers roughly half the time. Hunt for a dev process left running, a container scaled to two replicas, or an old deploy that never died. Polling does not scale horizontally; if you need more than one instance, use webhooks behind a load balancer. Details on the terminated-by-other-getUpdates page.

Both 409s are configuration states, not transient faults: retrying without changing anything reproduces them forever. The one-command diagnostic is getWebhookInfo - an empty url means polling mode, a populated one means webhook mode, and a URL you do not recognise means a stale deploy is stealing your updates.

400 Bad Request: sends that fail because of the message itself

The 400 family is the largest, and it splits by what you got wrong. First, the message content errors - these are deterministic, so the bot works for most messages and dies on specific ones.

{"ok":false,"error_code":400,"description":"Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 42"}

You set parse_mode and the text contains characters the parser reads as markup. MarkdownV2 requires escaping _ * [ ] ( ) ~ ` > # + - = | { } . ! even in prose - a sentence ending in a full stop can fail. Escape interpolated user content, or switch to HTML parse mode, which needs only &, <, >. The byte offset in the description points at the exact culprit; the can't parse entities page has escaping tables for both modes.

Bad Request: message is too long
Bad Request: message text is empty

Text messages cap at 4,096 characters - AI-generated replies overrun this constantly, so split on a paragraph boundary before sending (message is too long). The empty variant usually means an interpolation produced "" or a template variable never resolved (message text is empty).

Then the addressing errors:

Bad Request: chat not found
Bad Request: user not found
Bad Request: chat_id is empty

chat not found means the chat_id does not correspond to a chat your bot can reach: a username where a numeric ID is needed, a dropped negative sign (group IDs are negative, supergroups start with -100), a chat ID truncated by a 32-bit integer column, or a group the bot was removed from. A user ID your bot has never seen in an update gives user not found - Telegram bots cannot look up arbitrary users; they can only address chats they have met.

Try it yourself
Build your first chatbot free
Free plan, no credit card required. Live on your site in about 10 minutes.
Start building free

400 Bad Request: editing, deleting, and replying to messages

A second cluster fires when you manipulate existing messages. Each description names its own rule:

Literal descriptionThe rule you hit
message is not modifiedYour edit produced identical content - common in loops that re-render unchanged state. Compare before editing.
message can't be editedToo old, or not sent by your bot. Bots can only edit their own messages, within Telegram's edit window.
message to delete not foundAlready deleted, or the ID belongs to a different chat. Message IDs are per-chat, not global.
message to be replied not foundThe reply_to_message_id points at a deleted or foreign message. Send without the reply parameter as a fallback.
query is too old and response timeout expired or query ID is invalidYou answered a callback query too late. answerCallbackQuery must be called promptly - answer first, process after, or the user's button spins forever.

The callback-query row deserves emphasis because its symptom is so misleading: users report "the buttons stopped working", but the bot is receiving every tap - it is just answering after the query expired. The same acknowledge-first pattern that keeps webhooks healthy applies here: answer the callback immediately, then do the slow work.

400 Bad Request: group permissions and the supergroup migration

Group-related 400s look like bugs and are actually group administration:

Bad Request: have no rights to send a message
Bad Request: not enough rights to send text messages to the chat
Bad Request: CHAT_WRITE_FORBIDDEN

All three mean the bot is in the chat but muted: restricted by an admin, stripped of send permissions, or blocked from writing in that channel or topic. The fix is in the group's member settings, not in code - promote the bot or lift the restriction (have no rights to send a message, CHAT_WRITE_FORBIDDEN).

The one that breaks bots permanently:

{"ok":false,"error_code":400,"description":"Bad Request: group chat was upgraded to a supergroup chat","parameters":{"migrate_to_chat_id":-1001234567890}}

When a group upgrades to a supergroup its chat ID changes forever, and the old ID never works again. Telegram hands you the new ID in parameters.migrate_to_chat_id - store it the moment you see it, and also handle the migrate_to_chat_id field that arrives in updates when the migration happens. Bots that ignore this silently lose the group; the supergroup migration page has the handler pattern.

Telegram Bot Token Checker
Free tool - no signup, runs in your browser.
Open free tool

400 Bad Request: webhook registration refused

These fire at setWebhook time, which is good news - the failure is loud and immediate instead of silent:

Bad Request: bad webhook: HTTPS url must be provided for webhook
Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443
Bad Request: bad webhook: failed to resolve host

The rules behind them: webhooks are HTTPS-only with no development exception; only four ports are allowed, so the dev server on port 3000 can never receive a webhook (the ports 80, 88, 443, 8443 page covers the workarounds - a reverse proxy or a tunnel); and the hostname must resolve on the public internet, so localhost and internal DNS names are rejected (failed to resolve host).

Once registration succeeds, failures move to delivery time and stop being errors you receive - they become entries in getWebhookInfo's last_error_message field, which is where you look when a registered webhook goes quiet. That debugging path - certificates, timeouts, the acknowledge-then-process pattern - is shared across every platform, and our webhook debugging guide walks it in one pass. The mechanics of what a webhook actually is, if the term is new, take two minutes to absorb.

400 Bad Request: files, photos, and buttons

Media and keyboard errors round out the 400 family:

Literal descriptionCause and fix
wrong file identifier/HTTP URL specifiedThe file_id is malformed, from a different bot (file_ids are bot-scoped), or the URL is not a plain HTTPS file link.
file is too bigThe cloud Bot API caps downloads at 20MB and uploads at 50MB. A self-hosted Bot API server raises both to roughly 2000MB.
PHOTO_INVALID_DIMENSIONSWidth/height ratio or size outside Telegram's photo rules - send extreme aspect ratios as a document instead.
failed to get HTTP URL contentTelegram's servers could not fetch the URL you passed - dead link, auth-gated file, or a host blocking Telegram's fetchers. Host media somewhere publicly reachable.
BUTTON_URL_INVALID / BUTTON_DATA_INVALIDAn inline keyboard button carries a malformed URL, or callback_data over the 64-byte cap. Use short opaque tokens in callback_data and look the payload up server-side.

The 64-byte callback_data cap is the one that surprises people: serializing a JSON state object into the button works in testing and breaks the moment the state grows. Store state server-side, keyed by a short ID.

403 Forbidden: the recipient is the problem, and retrying is pointless

Every 403 means the same thing structurally: your request was valid, your token is fine, and the recipient is unreachable as a matter of policy. None of them are fixed by retrying - the correct response is always to update your database and stop sending.

{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}

The user blocked your bot. There is no API to check block status in advance - the send attempt is the check. Mark the chat inactive; bots that keep retrying blocked users burn their own rate limit (bot was blocked by the user).

Forbidden: bot can't initiate conversation with a user

The rule every notification system meets on day one: a Telegram bot can never message someone first. The user must send /start or tap a deep link like https://t.me/YourBotName?start=user_12345 before you may send anything. A phone number or user ID from another system grants nothing (can't initiate conversation).

The rest of the family, by literal string:

A well-behaved sender treats any 403 as a permanent state transition for that chat. Log it once, flag the record, move on.

429 Too Many Requests: flood control and retry_after

The bot that answered everything in testing and drops messages at launch is meeting flood control:

{"ok":false,"error_code":429,"description":"Too Many Requests: retry after 34","parameters":{"retry_after":34}}

Telegram's published guidance for bots: roughly 30 messages per second overall, about 1 message per second sustained to an individual chat, and around 20 messages per minute to a single group. The full set of ceilings lives on the Telegram limits page.

The contract when you exceed one: honour retry_after exactly. Retrying sooner extends the penalty, and sustained abuse can get a token limited far longer than the number suggests. The wrong fix is a sleep sprinkled into the send loop; the right fix is a token-bucket queue that paces broadcasts below 30 per second and spaces per-chat sends - the same architecture every platform ends up needing, as our chatbot API rate limiting guide covers cross-platform. Back-off rules and code patterns are on the 429 reference page, and the general concept on the rate limiting glossary page.

A subtler 429 trigger affects conversational bots: sending three quick bubbles to feel human - greeting, question, menu - can trip the per-chat limit for one user. Insert a short delay between bubbles; it also reads more naturally.

401 Unauthorized and 404 Not Found: the token itself

When every call fails, stop debugging payloads - the token is broken in one of two ways:

{"ok":false,"error_code":401,"description":"Unauthorized"}
{"ok":false,"error_code":404,"description":"Not Found"}

401 Unauthorized means the token has a valid shape but does not authenticate: revoked via BotFather (regenerating a token instantly kills the old one), or corrupted in transit into your environment - a trailing newline from a secrets manager, quote characters included in the value, or a truncated paste. 404 Not Found usually means the token is malformed enough that the URL path itself is wrong - a missing bot prefix (/bot<TOKEN>/getMe), a typo'd method name, or an empty token variable producing /bot/getMe.

Verify the token in isolation before touching anything else: open https://api.telegram.org/bot<TOKEN>/getMe in a browser, or use our Telegram bot token checker, which runs the same call from the browser and explains the response. A healthy token returns the bot's username and ID. If getMe succeeds locally but production returns 401, the token is fine and the deployed environment variable is not.

One trap with no error at all: a valid but wrong token - the staging bot's token deployed to production. Both tokens pass getMe, sends succeed, and users report silence because the replies are going out through a bot nobody is looking at. When logs show success and users see nothing, confirm which bot the token belongs to.

502 and 504: when the failure is not yours

Two gateway errors round out the list, and they matter mostly because of what you should not do about them:

  • 502 Bad Gateway - the Bot API server itself is having trouble, or a proxy between you and it is. Transient; retry with back-off.
  • 504 Gateway Timeout - the request did not complete in time. Also transient, but if it correlates with your own long-polling timeout settings, tune getUpdates' timeout parameter rather than hammering.

The rule for 5xx is the inverse of the 403 rule: these are the only errors in this guide where blind retrying is correct, provided it backs off. Everything else in this guide is deterministic - a 400 will fail identically on retry, a 403 is a policy state, a 409 is configuration. A retry queue that cannot tell these classes apart will hammer permanent errors and give up on transient ones; classify on error_code first, then on the description string.

Distinguish carefully between a 502 from Telegram and a 502 in your webhook's last_error_message - the second means Telegram called your server and your server answered 502, which is your app crashing, not Telegram's. getWebhookInfo tells you which side you are on.

Avoiding this layer altogether

Every error family above is transport plumbing: update-consumer exclusivity, escaping rules, per-chat pacing, token hygiene, migration handling. None of it is your product, and all of it recurs on every platform under different names - Meta's error codes, Slack's, LINE's. Handling it well is genuinely necessary if you operate the Bot API directly, and genuinely undifferentiating work if what you actually want is a bot that answers customers.

A managed platform absorbs this layer. With Conferbot's Telegram integration, you connect a bot token and build the conversation in a visual flow editor - webhook lifecycle, retry classification, rate-limit pacing, and message formatting are handled for you, and the same flow deploys to WhatsApp, LINE, and a website widget without re-implementing each platform's error table.

Start free with Conferbot - no credit card required, so you can compare a managed flow against your hand-rolled handler this afternoon.

If you are staying hands-on: bookmark the Telegram error directory for the string-level pages, keep the limits page next to your queue configuration, and when the bot is silent with no error in sight, work through the symptom-first guide - between the two, every failure in this family tree has a page.

Share this article:

Was this article helpful?

Ready to build your chatbot?

Join the businesses. Deploy on website, WhatsApp, and 11 more channels in minutes. Free forever plan available.

No credit cardNo coding13+ channels
Start Building Free

Get chatbot insights delivered weekly

Join 5,000+ professionals getting actionable AI chatbot strategies, industry benchmarks, and product updates.

🎯Automate this with a free chatbot

Build and deploy in 10 minutes. No coding needed.

FAQ

Telegram Bot API Error Codes FAQ

Everything you need to know about chatbots for telegram bot api error codes.

🔍
Popular:

Conflict: two things are competing for the same bot token's updates. Either a webhook is registered while your code polls with getUpdates - fix with deleteWebhook - or two processes are polling simultaneously, which splits messages between them roughly at random. Telegram allows exactly one update consumer per token. Call getWebhookInfo to see which state you are in before changing any code.

The chat_id does not correspond to a chat your bot can reach. Check for a username used where a numeric ID is required, a dropped negative sign - group IDs are negative and supergroup IDs start with -100 - a value truncated by a 32-bit integer column, or a group that upgraded to a supergroup and changed its ID. Store chat IDs as 64-bit integers or strings and handle migrate_to_chat_id.

Exactly what it says: the user blocked your bot, and there is no API to check block status in advance - the send attempt is the check. Mark that chat inactive in your database and stop sending, because retries will never succeed and only consume your rate limit. Treat every 403 in the family the same way: a permanent state change for that recipient, not a transient fault.

The number of seconds Telegram requires you to wait before the next request, returned in the parameters object alongside the literal description "Too Many Requests: retry after N". Honour it exactly - retrying sooner extends the penalty. If you hit 429 regularly, the structural fix is a token-bucket queue pacing sends below roughly 30 messages per second overall and about one per second per chat.

The token does not authenticate: it was regenerated in BotFather - which instantly invalidates the old value - or the environment variable is corrupted by a trailing newline, included quotes, or truncation. Test the token alone with getMe in a browser or a token checker tool. If getMe works locally but production fails, the token is fine and the deployed environment variable is what is broken.

401 Unauthorized means the token is well-formed but invalid - revoked or corrupted. 404 Not Found usually means the request URL itself is wrong: a missing bot prefix before the token, a typo in the method name, or an empty token variable collapsing the path. Both make every call fail, so verify the exact URL string and the token value together before debugging anything else.

Your parse_mode treats part of the text as markup. MarkdownV2 demands escaping for sixteen characters including underscore, brackets, hyphen, and even the full stop, so unescaped user content fails on names and product codes. Escape everything you interpolate, or switch to HTML parse mode, which only needs ampersand and angle brackets escaped. The byte offset in the error points at the exact failing character.

Upgrading a group to a supergroup permanently changes its chat ID, and the old ID stops working forever. Telegram tells you twice: the failed send returns "group chat was upgraded to a supergroup chat" with the new ID in parameters.migrate_to_chat_id, and an update carrying migrate_to_chat_id arrives at migration time. Store the new ID the moment you see either signal.

On the default cloud Bot API server, bots can download files up to 20MB and upload files up to 50MB; exceeding either returns "Bad Request: file is too big". Running Telegram's self-hostable Bot API server raises both limits to roughly 2000MB and also lifts the webhook port restriction - worthwhile only when large media is a real requirement rather than a default choice.

The callback_data on an inline button exceeds the 64-byte cap or contains invalid content. Serializing JSON state into the button is the usual cause - it fits during testing and overflows when the state grows. Store the state server-side keyed by a short opaque token, and put only that token in callback_data. The companion BUTTON_URL_INVALID means a button URL is malformed.

Only by class. Retry 5xx errors with back-off, and 429 after exactly retry_after seconds. Never blind-retry 400s - they are deterministic and will fail identically - and never retry 403s, which are permanent recipient states like blocks and kicks. The 409s are configuration conflicts that persist until you change the setup. A retry queue that cannot classify by error_code hammers permanent errors and abandons transient ones.

In getWebhookInfo. Registration problems fail loudly at setWebhook time with a bad webhook description, but once a webhook is registered, delivery failures stop being API responses - Telegram records the latest one in the last_error_message field, with a timestamp in last_error_date and a pending_update_count showing the backlog. A populated last_error_message plus a climbing count means your endpoint is rejecting every delivery.

About the Author

Content & Engineering

The Conferbot team writes about building, deploying, and improving AI chatbots.

View all articles
Skip the blank canvas
Start from one of 250+ free chatbot templates for lead generation, support, e-commerce, and 20+ industries - customize and launch in minutes.
Browse free templates

Related Articles

From the reference shelf

Fact-checked reference pages and free tools for the platform this article covers.

Omnichannel Platform

One Chatbot,
Every Channel

Your chatbot works seamlessly across WhatsApp, Messenger, Slack, and 6 more platforms. Build once, deploy everywhere.

View All Channels
Conferbot
online
Hi! How can I help you today?
I need pricing info
Conferbot
Active now
Welcome! What are you looking for?
Book a demo
Sure! Pick a time slot:
#support
Conferbot
New ticket from Sarah: "Can't access dashboard"
Auto-resolved. Password reset link sent.