Editorial

Telegram Chat ID: What the -100 Prefix Means

The minus sign and -100 are a trillion offset, not formatting. Telegram chat ID ranges, the 52-bit cap, and the migration bug.

JJyoti Ranjan SwainUpdated
Telegram chat ID ranges: a supergroup ID of -1001234567890 against a basic group ID of -821449

Your bot works in a private chat and goes silent in the group. The chat ID looked wrong, so you cleaned it up: dropped the minus sign, or trimmed the -100 that seemed like a typo. Telegram now answers 400 Bad Request: chat not found, and nothing in that reply tells you the characters you removed were carrying information.

The minus sign and the -100 are arithmetic. Telegram's four ID sequences (users, basic groups, supergroups and channels, secret chats) overlap in the underlying MTProto API, so the Bot API packs them into one signed range where the sign and prefix tell you which type you are holding. Here are the exact ranges from the official spec, and the three bugs they cause.

Table of contents

The four ranges

A Bot API dialog ID is a single 64-bit sequence built from the four MTProto sequences. Telegram documents the bounds precisely: the whole space runs from -4000000000000 to 1099511627775.

Telegram chat ID ranges: users positive, basic groups down to -999999999999, supergroups and channels below -1000000000001

Users are positive, from 1 to 0xffffffffff. An MTProto user ID and a Bot API user ID are the same number, no conversion.

Basic groups are negated: botApiChatId = -chatId, where the MTProto side runs 1 to 999999999999. So a basic group lands between -999999999999 and -1.

Supergroups and channels share one MTProto sequence running 1 to 997852516352. The conversion adds a trillion, then negates: botApiChannelId = -(1000000000000 + channelId). That gives -1997852516352 to -1000000000001, and it is where the -100 you see on screen comes from. It is the leading digits of 1000000000000, not a prefix string.

Secret chats sit outside the bot's world; their MTProto IDs run -2147483648 to 2147483647, and bots never see them.

One consequence catches people out: a channel ID and a supergroup ID are indistinguishable by range, because they are the same sequence. If your code needs to know which it has, read chat.type from the update, which is private, group, supergroup or channel.

Why the offset exists at all

Because without it, ID 4823 would be ambiguous. It could be user 4823, basic group 4823, or channel 4823, all three of which exist independently in MTProto. The Bot API had to make one flat namespace, so it spent the sign bit on "not a user" and a trillion-offset on "not a basic group."

The practical payoff is that range checks alone tell you the peer type before you have parsed anything else, which is exactly why the docs recommend converting MTProto IDs to Bot API form even when you keep separate tables per peer type.

Bug 1: the 52-bit float

Telegram's own wording on the Chat.id field is a warning, not a footnote: the number may have more than 32 significant bits and "some programming languages may have difficulty/silent defects in interpreting it." It caps the damage by promising at most 52 significant bits, so a signed 64-bit integer or a double-precision float both hold it exactly.

52 bits is not an arbitrary number. It is the mantissa of an IEEE-754 double, which is what a JSON number becomes in JavaScript. Telegram sized the ID space to survive JSON.parse.

What still breaks is a 32-bit integer column. -1001234567890 does not fit in INT, and a database or language that silently truncates gives you a plausible looking wrong number. Store chat IDs as BIGINT, int64, Python int, or a string you never do arithmetic on.

Bug 2: the group that became a supergroup

This is the one that breaks working code with no deploy. Add a member past the limit, enable public history, or attach a discussion group to a channel, and Telegram migrates the basic group to a supergroup. The chat ID changes range mid-life, from the -999999999999 band into the -1000000000001 band.

Telegram does tell you. The migration event arrives as a service message carrying migrate_to_chat_id on the old chat, and the new supergroup's first message carries migrate_from_chat_id. Both fields carry the same 52-bit caveat as Chat.id.

Group migration: a basic group ID at -821449 becomes a supergroup ID at -1001821449 via migrate_to_chat_id

Most bots ignore both fields, keep sending to the stored basic group ID, and start collecting chat not found from a group that visibly still exists. If your bot persists chat IDs, handle migrate_to_chat_id by updating the stored row. It is a dozen lines that prevent a silent outage months later.

Bug 3: the 409 that hides your ID

Before you can read a chat ID out of getUpdates, the bot has to be allowed to poll. Telegram's note on the method is one line: "This method will not work if an outgoing webhook is set up." A bot that has ever run in production almost certainly has one, and you get 409 Conflict instead of your updates.

The fix is deleteWebhook, which switches the bot back to polling and returns True. Two things to know before you fire it. It accepts drop_pending_updates, and passing True discards the queue you were trying to read, so leave it off for an ID lookup. And if that bot's server is live, it will re-register its webhook on the next call, so point it back when you are done.

Also worth knowing: undelivered updates are held server-side for a maximum of 24 hours. If the group message you are looking for is from last week, it is gone. Send a fresh one.

The Telegram Chat ID Finder does this sequence in the browser: validates the token, names the bot so you know which one you are holding, detects the 409 and offers the deleteWebhook call, then reports the ID with its sign and offset intact. The token goes from your device to api.telegram.org and nowhere else.

Reading the ID out of getUpdates

If you would rather do it by hand, the shape of the call matters more than the tooling.

bash
curl -s "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates" \
  | python3 -c 'import json,sys
for u in json.load(sys.stdin)["result"]:
    m = u.get("message") or u.get("channel_post") or {}
    c = m.get("chat", {})
    print(c.get("type"), c.get("id"), c.get("title") or c.get("username"))'

Three details decide whether you get anything back. Group messages arrive under message, but channel posts arrive under channel_post, so a channel lookup that only reads message finds nothing. In a group with privacy mode on, the bot only sees commands addressed to it, so send /start@your_bot rather than "hello". And for a channel, the bot needs to be an admin before any post reaches it.

If you get an empty result and no error, the bot is fine and the queue is empty. Post again and re-run.

When a username is the better key

For a public channel or supergroup you can skip the numeric ID entirely and pass @channelusername as chat_id. It reads better in config files and survives nothing, which is the tradeoff: usernames can be changed or released by their owner, and then your bot is posting to whoever claimed it next.

Numeric IDs never change for the lifetime of a chat, apart from the migration case above. For anything private, or anything you care about, store the number. Keep the username as a label for humans reading the config.

FAQ

What does the -100 in a Telegram chat ID mean? It is the leading digits of the trillion offset the Bot API adds to supergroup and channel IDs before negating them: botApiChannelId = -(1000000000000 + channelId). Removing it produces an ID in the basic-group range that does not exist, and Telegram replies chat not found.

Why is my group chat ID negative? Basic group IDs are negated MTProto IDs, so the sign is how the Bot API separates them from user IDs, which are positive. Both sequences start at 1 in MTProto and would otherwise collide.

Can a Telegram chat ID change? Yes, in one case. When a basic group is migrated to a supergroup, the ID moves from the -999999999999 band to the -1000000000001 band. Telegram sends migrate_to_chat_id on the old chat and migrate_from_chat_id on the new one.

What is the range of a valid Telegram chat ID? The whole Bot API dialog space runs from -4000000000000 to 1099511627775. Users are 1 to 0xffffffffff, basic groups -999999999999 to -1, supergroups and channels -1997852516352 to -1000000000001.

How do I tell a channel ID from a supergroup ID? Not by the number. They share one MTProto sequence and therefore one Bot API range. Read chat.type from the update, which returns supergroup or channel.

Why does getUpdates return 409 Conflict? The bot has a webhook registered, and Telegram allows one delivery method at a time. Call deleteWebhook to switch to polling, without drop_pending_updates if you still want the queued messages.

How long does Telegram keep updates my bot has not fetched? Up to 24 hours. After that they are discarded whether you polled or not, so an old group message cannot be recovered by calling getUpdates today.

Is it safe to store a chat ID as a 32-bit integer? No. A supergroup ID like -1001234567890 exceeds 32 bits. Telegram guarantees at most 52 significant bits, which fits BIGINT, int64, or a JavaScript number, but silently truncates in an INT column.

Conclusion

The sign and the -100 are the type tag. Strip them and you are asking Telegram about a chat that was never created.

Store the full value in a 64-bit column, read chat.type when you need to know what kind of chat you have, and handle migrate_to_chat_id if you keep IDs around for more than a day. That covers every ID-shaped failure a bot runs into.

To read the ID for a chat you control without setting up polling yourself, the Telegram Chat ID Finder handles the webhook conflict and returns the value unedited. If your bot is going to post computed numbers, the JSON Formatter is handy for eyeballing a getUpdates payload before you write the parser.

Sources

Tools In This Article

Browser-based, no sign-up. Try them while the topic is fresh.

More From ToolMintX

Other Blog Posts