What it means
When a Telegram client logs in over MTProto, it does not receive a token in the way an OAuth client would. It performs a Diffie-Hellman key exchange with the server and both sides end up holding the same 2048-bit shared secret, 256 bytes, called the auth key. Every request from then on is encrypted with a key derived from it, and the server identifies the client by the auth_key_id, which is the low 64 bits of the SHA-1 of that key.
A session string is that auth key plus enough routing information to reconnect, packed into a single base64 blob you can copy and paste. It exists so a client can be stopped, moved, containerised or redeployed without going through the SMS login flow every time.
The exact packing is library-specific. Telethon's StringSession is a version byte, the data centre ID, the server IP and port, and the 256-byte auth key, base64-encoded, which lands at roughly 350 characters. Pyrogram packs a little more: the API ID, a test-mode flag, the user ID and a bot flag. WTelegramClient, the MTProto library behind Pinlyx, skips strings altogether and persists an encrypted session file. All three are the same idea with different envelopes.
The security model, stated plainly
A session string is a bearer credential of the strongest kind available on Telegram. Whoever holds it can:
- Read every private chat, group and channel on that account.
- Send messages as that person, including to their contacts.
- Export the account's contact list and dialog history.
- Terminate other sessions on the account.
And the point that surprises people: two-factor authentication does not help. The 2FA password guards the login flow. Once an auth key exists, no subsequent request consults the password again. Adding 2FA to an account whose session has already leaked changes nothing at all.
The only real control is revocation. Settings, Devices, terminate the session, and the server drops the auth key. Programmatically that is account.getAuthorizations to enumerate and account.resetAuthorization(hash) to kill one.
This is why a session string should never be pasted into a chat, a support ticket, a bug report, an environment file committed to git, or a log line. Any "free Telegram tool" that asks you to paste a session string is asking for permanent, unrevoked access to the account, and the request is almost never necessary: legitimate tools run the login flow themselves.
AUTH_KEY_DUPLICATED: the deployment trap
The single most common way to destroy a working session in production has nothing to do with attackers.
Telegram treats one auth key being used from two different places simultaneously as evidence of key theft, and it responds by invalidating the key. The error is AUTH_KEY_DUPLICATED, status 406, and it is permanent. There is no back-off that recovers from it; the account has to log in again from scratch, which for a phone-number account means another SMS code.
The realistic causes are all ordinary operations mistakes:
- A rolling deploy where the new container connects before the old one has finished shutting down.
- Two replicas of the same worker scheduled onto different nodes, both reading the same session from shared storage.
- A developer running the same session locally to debug while it is live on the server.
- A cron job and a long-running daemon both instantiating a client from the same file.
The structural fix is to make the session single-owner: one process holds it, and everything else talks to that process. If you must share, take a lock. Draining the old process fully before starting the new one is not optional with MTProto sessions in the way it can be with stateless HTTP workers.
Sessions carry state, not just keys
A session is often described as "just the auth key", and that description costs teams a lot of unnecessary traffic.
A complete session also holds the update state: the pts, qts, date and seq counters that mark how far the client has consumed Telegram's event stream. Restore only the key and the client has to call updates.getState and refill from a cold start, which produces a burst of catch-up requests on every restart.
Most session implementations also hold a peer cache, the mapping of IDs to access hashes that the account has resolved. Throwing that away means re-resolving every contact, and mass re-resolution is one of the reliable ways to earn a flood wait. In other words, an aggressive "just regenerate the session" habit is not neutral: it costs rate limit budget every time.
Data centres and exported authorizations
An auth key is bound to one Telegram data centre. Telegram runs several, and an account is homed to whichever one it registered against.
That matters when a file lives elsewhere. Media in a chat may be stored in a different data centre from the one your session is authorised against, and downloading it requires a separate authorization for that data centre, obtained with auth.exportAuthorization on the home data centre and auth.importAuthorization on the target. Libraries usually handle this transparently, which is why it only becomes visible when you write your own transport and downloads mysteriously fail with AUTH_KEY_UNREGISTERED against a data centre you never logged into.
Why it matters
For anything running Telegram at scale, the session store is the crown jewels. A CRM with 40 connected accounts is holding 40 credentials, each of which grants total access to a real person's Telegram, including their private conversations that have nothing to do with work.
That places concrete obligations on the software: encryption at rest with a key held outside the database, no session material in logs or error reports, no session material in API responses, and a revocation path the account owner can trigger themselves without asking support. It also places an obligation on the operator to treat "the session died" as an event worth investigating rather than an error to auto-retry, because the difference between a rolling deploy bug and an actual compromise is visible in exactly one place: the error code.
Common mistakes
- Storing session strings in environment variables. They end up in process listings, crash dumps, and CI logs. Use an encrypted field in the database or a secrets manager.
- Retrying AUTH_KEY_UNREGISTERED. The key is gone. Retrying just adds failed handshakes to the account's record.
- Running one session in two processes. This is not a race condition to be managed, it is a session-destroying event.
- Regenerating sessions casually. Every fresh session throws away the update state and the peer cache and pays for both again.
- Assuming 2FA protects an existing session. It does not. Only revocation does.
- Backing up session files to unencrypted object storage. A backup bucket with a session file in it is a backup bucket with a logged-in Telegram account in it.
Related concepts
- MTProto: the protocol whose handshake produces the auth key in the first place.
- Userbot: the pattern that makes session strings necessary, and risky.
- Access hash: the per-account peer authorizations that live alongside the session.
- Telegram Bot API: the alternative where the credential is a revocable bot token instead.
- TDLib: keeps the same material in a local encrypted database directory rather than a string.
- Flood wait: what unnecessary session churn costs you in rate limit budget.
How Pinlyx handles it
Pinlyx never asks a customer to paste a session string. Accounts are connected through the normal Telegram login flow inside the panel, the resulting session is persisted encrypted at rest, and the encryption key lives outside the database. Each session has exactly one owning worker process, so a rolling deploy drains before it starts and AUTH_KEY_DUPLICATED stays theoretical. Session errors are classified rather than retried: a 406 pages the operator, a 401 marks the account disconnected and prompts the owner to re-link it, and a ban stops every queued send for that number immediately.