· 5 min read

Telegram Bot Security: Stop Trusting the Username

A common Telegram bot security mistake is treating a user’s username as a stable identity. Usernames can change, disappear, or be reused, so any access control built on them is fragile. This guide shows how to use Telegram IDs correctly and harden your bot’s authorization logic.

One of the most common security mistakes in Telegram bot development is using a username as if it were a permanent identity.

That seems convenient at first:

  • it is human-readable
  • it is easy to log
  • it looks unique
  • it is available in many update payloads

But a Telegram username is not a reliable security boundary. If your bot uses usernames for authorization, access control, whitelisting, or account linking, you are building on something that can change.

This guide explains why that is dangerous and what to do instead.

The mistake: using username for authorization

A typical vulnerable pattern looks like this:

ALLOWED_USERS = {"alice_admin", "bob_ops"}

if message.from_user.username in ALLOWED_USERS:
    grant_admin_actions()

Or this:

if (update.message.from.username === process.env.ADMIN_USERNAME) {
  allowSensitiveCommand();
}

This feels fine until one of the following happens:

  • the user changes their username
  • the username is released and claimed by someone else
  • the username is missing entirely
  • your bot receives an update with no username field
  • you use the username as a key in a database and later cannot match the real user

In all of these cases, the bot’s security logic becomes unreliable.

Why usernames are unsafe as identity

Telegram usernames are not the same thing as user IDs.

A Telegram user ID is a numeric identifier that stays stable for that account. A username is just an alias.

Security problems with usernames:

1. Usernames can change

A legitimate user may rename themselves. If your bot trusts @alice_admin, that trust can suddenly point to nobody.

2. Usernames can be absent

Many Telegram users do not set a username at all. If your bot treats username == null as “unknown” and therefore “safe to deny,” that is okay for denial, but not for identity.

3. Usernames can be reused

Even if Telegram’s rules limit reuse in practice, your security model should not depend on a name never being reassigned.

4. Usernames are not a secret

A username is public. Anyone can see it, mention it, and often guess it.

5. Usernames are easy to spoof in logs and UI

A malicious user can pick a similar name, and humans reviewing logs may confuse one account with another.

Use Telegram id as the primary identity

For authorization, persistence, and account binding, use:

  • message.from.id
  • callback_query.from.id
  • inline_query.from.id
  • chat.id for chat-scoped permissions

The user ID is what you should store and compare.

Safe example

ALLOWED_ADMIN_IDS = {123456789, 987654321}

user_id = message.from_user.id
if user_id in ALLOWED_ADMIN_IDS:
    grant_admin_actions()

This is better because:

  • the ID is stable for that Telegram account
  • it does not depend on display name changes
  • it is unambiguous
  • it works even when the user has no username

If you need a username, treat it as metadata only

Usernames are still useful for:

  • showing a human-readable label in logs
  • generating links in a UI
  • making admin dashboards easier to scan

But they should be treated as presentation data, not proof of identity.

Good pattern:

{
  "user_id": 123456789,
  "username": "alice_admin",
  "first_seen": "2026-01-10T12:00:00Z"
}

Then make security decisions only from user_id.

Common places this mistake appears

1. Admin commands

Bad:

if message.from_user.username == "myadmin":
    handle_admin_command()

Better:

if message.from_user.id == 123456789:
    handle_admin_command()

2. User-to-account linking

Bad:

  • user types @alice
  • bot stores @alice as their identity
  • later @alice changes to @alice2
  • the account link breaks or, worse, attaches to the wrong person

Better:

  • ask the user to authenticate once
  • store the Telegram numeric ID
  • optionally cache the username as a display field

3. Webhook access control logic

Some developers incorrectly inspect usernames to decide whether a request is privileged. The bot should validate the Telegram payload, then authorize based on numeric IDs and chat context.

4. Database keys

Bad schema:

PRIMARY KEY(username)

Better schema:

PRIMARY KEY(user_id)

You can still index username for search, but not for trust.

Handle chats and groups correctly

In group bots, security decisions often involve both the user and the chat.

For example:

  • only allow admins in a specific group
  • only process commands in a private chat
  • only accept callbacks from a message in a known chat

Use both IDs where relevant:

  • from_user.id for who performed the action
  • chat.id for where it happened

Example:

ALLOWED_GROUP_IDS = {-1001234567890}
AUTHORIZED_USERS = {123456789}

if message.chat.id in ALLOWED_GROUP_IDS and message.from_user.id in AUTHORIZED_USERS:
    process_sensitive_group_command()

Do not replace either check with usernames.

Protect callback queries the same way

Callback queries are a frequent place for authorization bugs.

If you generate inline keyboard buttons for an admin-only action, you must verify the person pressing the button is still allowed to do so.

Bad assumption:

  • “I already sent this button to an admin, so anyone who presses it must be fine.”

That is not true. A forwarded message, shared chat, or stale UI can break that assumption.

Safer pattern:

def on_callback(query):
    if query.from_user.id not in ALLOWED_ADMIN_IDS:
        return query.answer("Not authorized", show_alert=True)

    handle_admin_callback(query)

Do not trust the sender name or username of the callback source.

Build a permission model around IDs, not names

A good authorization model for a Telegram bot typically looks like this:

  • user ID: identifies the account
  • chat ID: identifies the conversation or group
  • role mapping: stores what that user can do
  • event checks: confirm both the actor and the context

Example role table:

{
  "123456789": "owner",
  "987654321": "moderator"
}

Example logic:

role = roles.get(str(message.from_user.id))
if role not in {"owner", "moderator"}:
    deny()

If the username changes, your role mapping stays correct.

What to log

Log enough to investigate security events without depending on usernames.

Recommended fields:

  • user ID
  • chat ID
  • command or callback data
  • timestamp
  • result of authorization
  • username only as optional context

Example log entry:

{
  "event": "admin_command",
  "user_id": 123456789,
  "username": "alice_admin",
  "chat_id": -1001234567890,
  "allowed": true
}

If a username changes later, the log is still attributable.

Migration checklist if your bot already trusts usernames

If you already shipped code that uses usernames, fix it in this order:

  1. Add user ID storage alongside any existing username fields.
  2. Switch authorization checks to user IDs.
  3. Backfill mappings from old records where possible.
  4. Review admin commands and callbacks for username-based checks.
  5. Keep username as display metadata only.
  6. Test account rename scenarios to verify the bot still recognizes the right user.

A good migration often involves running both fields temporarily:

allowed = (
    message.from_user.id in ALLOWED_ADMIN_IDS or
    message.from_user.username in LEGACY_ALLOWED_USERNAMES
)

Then remove the legacy username path as soon as you can.

A practical rule of thumb

If a field can be changed by the user, it should not be the basis for trust.

That includes:

  • username
  • display name
  • profile photo
  • bio

Use stable identifiers for trust, and mutable fields for presentation.

Final takeaway

Telegram bot security gets much stronger when you stop treating usernames like identity.

Use Telegram numeric IDs for authorization, persistence, and audit trails. Keep usernames as convenience metadata only. That one design choice removes an entire class of bugs that are easy to miss and painful to fix later.

If your bot currently says, “only @some_name can do this,” it is time to change that to “only Telegram user ID X can do this.”

← All posts