GLOSSARY

What is a Peer ID?

A peer ID is the numeric identifier Telegram gives to a user, a basic group, or a channel. MTProto keeps those three as separate ID namespaces, while the Telegram Bot API flattens them into one signed chat_id using sign and a -100 prefix, which is why the same conversation has two different numbers depending on which API you are looking at.

Free forever plan · No credit card required · Cancel anytime

Quick definition

A peer ID is the numeric identifier Telegram gives to a user, a basic group, or a channel. MTProto keeps those three as separate ID namespaces, while the Telegram Bot API flattens them into one signed chat_id using sign and a -100 prefix, which is why the same conversation has two different numbers depending on which API you are looking at.

In a single sentence: MTProto asks "which kind of peer, and which one", the Bot API encodes both answers into one number.

What it means

A peer in Telegram is anything you can hold a conversation with: a person, a small group, or a channel (which includes supergroups). A peer ID is the number that identifies one of them.

The subtlety is that Telegram has two APIs and they disagree about what that number looks like. In MTProto, the peer is a tagged union: peerUser(user_id), peerChat(chat_id), and peerChannel(channel_id). The type tag carries the namespace, so the raw numbers are allowed to overlap. User 42, basic group 42, and channel 42 can all exist at the same time and mean three unrelated things.

The Telegram Bot API has no type tag. It has a single field called chat_id, and it has to make the three namespaces fit into it without collisions. That is where the encoding comes from, and it is why a group ID in a webhook payload looks like -1001234567890 while the same group in an MTProto trace looks like 1234567890.

The encoding, precisely

Users keep their number and stay positive. Basic groups have their sign flipped. Channels and supergroups have 1000000000000 added and then the whole thing negated, which produces the familiar -100 prefix when you read the digits left to right.

The reverse conversions matter just as much, because half of the Telegram ecosystem speaks one dialect and half speaks the other. A deep link like t.me/c/1234567890/42 uses the raw channel ID with no prefix at all, so building that URL from a Bot API chat_id means stripping the prefix first. Get that wrong and every "open in Telegram" link in your CRM 404s.

The 52-bit problem

Telegram IDs used to be comfortably inside a 32-bit signed integer, and a great deal of software was written on that assumption. That stopped being true. Bot API 5.0 carried an explicit warning that user IDs could now exceed 32 bits and might use up to 52 significant bits, with the same warning later extended to supergroup and channel IDs.

Fifty-two bits is a very specific number, and it is not an accident: it is what fits exactly inside the mantissa of an IEEE 754 double-precision float. Telegram chose it so that JavaScript, PHP and every other language whose default numeric type is a double can still represent a Telegram ID without losing digits.

The practical consequences:

  • Database columns are BIGINT, never INTEGER. A 32-bit column truncates silently and you discover it months later as contacts that cannot be messaged.
  • In C#, long. In PostgreSQL, bigint. In TypeScript, a number is fine for IDs but not for the matching access hash, which is a full 64-bit value and does overflow a double.
  • Any JSON boundary in your stack is a risk. If an int64 hash rides through a JavaScript parser, the low digits change and nothing logs an error.

Migration: the ID that dies

Peer IDs are stable, with exactly one important exception. When a basic group is upgraded to a supergroup, Telegram does not change the type of the existing object. It creates a new channel, copies the members across, and deactivates the old chat.

The old chat receives a messageActionChatMigrateTo(channel_id) service message and the new supergroup opens with a messageActionChannelMigrateFrom(title, chat_id). Those two service messages are the only bridge between the old ID and the new one, and a client that ignores them is left with a stored ID that now resolves to a dead group.

For a CRM this is a data-integrity event, not a cosmetic one. Every conversation, note, and pipeline card attached to the old chat ID has to be re-pointed at the new channel ID, or the whole history silently detaches from the live conversation.

Message IDs are scoped too

Peer IDs are only half of any message reference. The other half, the message ID, has its own scoping rule that surprises people.

In a channel or supergroup, message IDs are chat-scoped. They start at 1 and increment for that chat, and every member sees the same message with the same ID. That is what makes t.me/c/<channel_id>/<msg_id> a stable permalink.

In private chats and basic groups, message IDs come from your own account's message box, which is shared across all of your private conversations. The message you sent has one ID for you and a different ID for the person who received it. There is no permalink, and there is no way to reference "message 812" without also saying whose box you mean.

The storage rule that follows is simple and non-negotiable: the primary key for a message row is (account_id, peer_id, message_id). Anything shorter will collide.

Why it matters

Peer IDs are the join key for everything a Telegram CRM does. Conversations, contacts, pipeline stages, assignment rules, automation triggers and analytics all hang off them. Get the representation wrong once, at the schema level, and every feature built on top inherits the bug.

The failure modes are quiet by design. A truncated 32-bit ID still looks like a plausible number. A Bot API chat_id stored in a column that a MTProto sender reads will target a peer that does not exist rather than throwing a type error. A migrated group looks like a customer who suddenly stopped replying. None of these raise an exception, which is exactly why they survive to production.

Common mistakes

  • Mixing the two dialects in one column. Pick one canonical representation for your database and convert at the edges. Most teams store the MTProto form plus a peer-type enum, because it is lossless.
  • Using ABS() to "normalise" an ID. Stripping the sign destroys the namespace tag. A supergroup and a user can then collide in the same table.
  • Building t.me/c links from a Bot API chat_id. The -100 prefix has to come off first.
  • Assuming a peer ID identifies a person. It identifies an account. Deleted accounts keep their ID; the profile just empties out.
  • Storing message IDs without their peer. Fine in a channel, actively wrong in private chats.
  • Importing system peers as contacts. 777000 is Telegram's own service account, not a lead.

Related concepts

  • Access hash: the other half of an MTProto peer reference, and the part that is not portable between accounts.
  • Supergroup: the peer type that lives in the channel namespace and inherits the -100 prefix.
  • Telegram Bot API: where the flattened chat_id encoding comes from.
  • MTProto: where the three separate namespaces come from.
  • Forum topic: the reason a peer ID alone is no longer a unique conversation key.
  • Webhook: the payload where a Bot API chat_id most often enters your system.

How Pinlyx handles it

Pinlyx stores peers in MTProto form with an explicit peer-type column and converts to the Bot API encoding only at the boundary where a payload requires it, so no single column ever holds two dialects. All ID columns are bigint in PostgreSQL and long in the .NET API. Migration service messages are watched, and when a basic group becomes a supergroup the linked conversation, notes and pipeline card follow the new channel ID rather than being orphaned. System peers are filtered out of contact import so nobody ends up with Telegram itself sitting in their Telegram CRM pipeline.

Decision table · which number am I holding

MTProto peer to Bot API chat_id, in both directions.

Three rules cover every conversation on the platform.

User

MTProto
peerUser(user_id)
Bot API
chat_id = user_id

Positive, unchanged. The only case where the two APIs agree on the number.

Basic group

MTProto
peerChat(chat_id)
Bot API
chat_id = -chat_id

Sign flip only. A basic group that later migrates to a supergroup gets a brand new ID in a different namespace.

Supergroup / channel

MTProto
peerChannel(channel_id)
Bot API
chat_id = -1000000000000 - channel_id

The famous -100 prefix. Reverse it with channel_id = -chat_id - 1000000000000.

Worked example
// One supergroup, three representations
raw channel_id      1234567890
Bot API chat_id    -1001234567890     // -1000000000000 - 1234567890
permalink           https://t.me/c/1234567890/42

// Round trip, both directions
const toBotApi   = (channelId) => -1000000000000 - channelId;
const toChannelId = (chatId)   => -chatId - 1000000000000;

// The trap: never do this
const normalized = Math.abs(chatId);   // destroys the namespace tag

// A user in both dialects
peerUser(641307856)  ->  chat_id 641307856      // unchanged, positive

// A basic group in both dialects
peerChat(287451093)  ->  chat_id -287451093     // sign flip only
The one ID that changes

Group upgrades break stored IDs.

A basic group upgraded to a supergroup does not keep its identity. Telegram creates a new channel with a new ID in a different namespace, and the old chat is deactivated. The only link between them is a pair of service messages: messageActionChatMigrateTo in the old chat and messageActionChannelMigrateFrom in the new one.

If your integration does not watch for those, the symptom looks like a customer who stopped replying, and the cause is a foreign key pointing at a group that no longer receives messages.

Schema checklist

Six rules for storing Telegram IDs.

  • Every ID column is 64-bit. BIGINT in PostgreSQL, long in C#, no exceptions.
  • Store the peer type next to the ID so you never have to infer it from the sign.
  • Pick one dialect as canonical and convert only at API boundaries.
  • Key messages on (account_id, peer_id, message_id), because message IDs are not globally unique.
  • Handle migration service messages and re-point the conversation at the new channel ID.
  • Exclude system peers such as 777000 from contact import and lead scoring.
Watch out for

An ID is not a permission.

Knowing someone's peer ID does not let you message them. Over the Bot API you still need them to have started your bot; over MTProto you still need an access hash that your specific account obtained. Any tool that promises to DM a list of raw numeric IDs is either wrong about how Telegram works, or is planning to burn accounts resolving them.

Peer IDs: FAQ

What every developer asks the first time they see a chat ID starting with -100.

Because the Bot API has to squeeze three separate MTProto namespaces into a single signed integer. Users stay positive, basic groups are made negative by flipping the sign, and channels and supergroups get -1000000000000 subtracted from their raw channel_id, which reads as a "-100" prefix when you look at the digits. Without that scheme, a user with ID 12345 and a channel with ID 12345 would collide in a single chat_id column.
No. Telegram warned in Bot API 5.0 that user IDs can exceed 32 bits and may use up to 52 significant bits, and the same warning was later extended to supergroup and channel IDs. The official guidance is a 64-bit integer, or a floating point type with at least 53 significant bits. In practice that means BIGINT in PostgreSQL and long in C#. A 32-bit column does not fail loudly, it silently truncates, which is far worse.
Over the Bot API, yes, provided the user has already started your bot. Over MTProto, no. There you also need the access hash, which is the per-account authorization that proves your client is allowed to reference that peer. An ID without a matching access hash produces PEER_ID_INVALID.
A user ID is stable for the life of the account. A channel ID is stable for the life of the channel. The one case that does change is migration: when a basic group is upgraded to a supergroup, Telegram creates a new channel object with a new ID in a different namespace and marks the old chat as deactivated. Any CRM row still pointing at the old chat ID is now pointing at a dead conversation.
It depends on the peer type, and this trips people up constantly. In channels and supergroups, message IDs are chat-scoped: everyone in the chat sees the same message with the same ID, starting from 1 and counting up. In private chats and basic groups, message IDs come from your own account message box, so the same message carries a different ID for the sender and the receiver. Either way, the safe storage key is the pair (peer_id, message_id), never message_id on its own.
Telegram reserves a handful of system accounts. 777000 is Telegram itself, the account that delivers login codes and service notifications. 1087968824 is GroupAnonymousBot, which appears as the sender when an admin posts anonymously in a group. 136817688 is Channel_Bot, which appears as the sender when a channel post is auto-forwarded into its linked discussion group. Treat all three as system peers and exclude them from contact imports and lead scoring.
Ready to ship

One contact record. Every Telegram identifier handled.

Pinlyx normalises peer IDs, tracks group migrations, and keeps the Bot API and MTProto dialects from ever meeting in the same column.

Free forever plan · GDPR-ready · No credit card required

We value your privacy

We use cookies to improve our site, analyze traffic, and personalize ads. You can accept all, reject non-essential, or customize your choices. Read our Cookie Policy.