· 8 min read

Securing Telegram Mini Apps: Verifying Init Data, Hardening Auth, and Avoiding Trust Bugs

Telegram Mini Apps are convenient, but convenience can hide dangerous trust assumptions. This guide focuses on the most important security control: treating everything from the client as untrusted and verifying Telegram-signed init data correctly on the server.

Telegram Mini Apps are built to feel native inside chats, but that convenience creates a security trap: developers often trust fields coming from the client without proving they came from Telegram. In practice, the most important security boundary in a Mini App is not the WebView itself, but your server’s ability to verify that a request actually originated from a real Telegram session.

This guide focuses on one security concern that causes the most damage in production: forging user identity by trusting client-provided data. If you build Mini Apps, you should treat the browser, the JavaScript bridge, and any payload received from the front end as attacker-controlled until the backend validates it.

The core rule: never trust user.id from the browser

A common mistake looks like this:

  • the Mini App reads window.Telegram.WebApp.initDataUnsafe.user.id
  • the client sends that user.id to your API
  • the server uses it as the authenticated user identifier

This is insecure because initDataUnsafe is explicitly unsafe: it is convenient for UI rendering, but it is not a security proof. An attacker can modify requests, replay values, or fake the entire payload if your backend accepts it without verification.

The correct model is:

  1. Telegram opens the Mini App and provides signed init data.
  2. The client forwards the raw initData string to your backend.
  3. Your backend verifies the signature using your bot token.
  4. Only after verification do you trust the embedded user identity and metadata.

What must be verified

Telegram Mini Apps include an initData string that contains key-value pairs such as:

  • user
  • chat_instance
  • auth_date
  • query_id
  • hash

The security control is the hash. Your server must reconstruct the signing payload exactly as specified by Telegram and compare the computed HMAC to the supplied hash.

If this comparison fails, the request must be rejected.

Important properties to enforce:

  • the hash comparison must be constant-time
  • auth_date must be recent enough to prevent replay
  • all parsing must preserve the exact canonical form used in verification
  • the bot token must never be exposed to the client

Server-side verification flow

A robust verification flow should look like this:

  1. Receive initData as a raw string from the client.
  2. Parse it into key-value pairs.
  3. Remove the hash parameter from the set used to compute the check string.
  4. Sort the remaining parameters lexicographically by key.
  5. Join them as key=value lines separated by newline characters.
  6. Compute the secret key from the bot token.
  7. Compute the HMAC over the check string.
  8. Compare the resulting digest with the provided hash using constant-time comparison.
  9. Check auth_date against an acceptable freshness window.
  10. If valid, extract the Telegram user and create or continue the session.

If any step fails, do not partially accept the request.

Example backend implementation pattern

Below is a simplified pattern in pseudocode. Adapt the cryptographic primitives to your stack.

function verifyInitData(initData, botToken):
    params = parseQueryString(initData)
    receivedHash = params["hash"]
    remove params["hash"]

    checkString = join(
        sortByKey(params)
            .map(key => key + "=" + params[key]),
        "\n"
    )

    secretKey = HMAC_SHA256(key="WebAppData", message=botToken)
    computedHash = HMAC_SHA256(key=secretKey, message=checkString)

    if not constantTimeEqual(hex(computedHash), receivedHash):
        reject("invalid signature")

    authDate = int(params["auth_date"])
    if now() - authDate > 86400:
        reject("stale init data")

    return params

There are two implementation details that often go wrong:

1. The signing key derivation is not optional

Do not reuse the bot token directly as the HMAC key unless your framework or Telegram’s current specification explicitly says so for the verification step you are implementing. Follow the exact derivation rule for Mini App init data verification in the documentation you are targeting.

2. String canonicalization must be exact

Verification fails if you:

  • sort incorrectly
  • decode and re-encode values inconsistently
  • normalize characters unexpectedly
  • drop empty values
  • alter newline handling

The backend should validate the raw incoming string, not a JSON object reconstructed by the client.

Use initDataUnsafe only for display

The Telegram JavaScript bridge exposes both safe and unsafe forms of init data for a reason.

  • initDataUnsafe is fine for immediate UI presentation, such as showing the user’s display name or avatar before the server round-trip completes.
  • It is not acceptable as proof of identity.

A safe pattern is:

  • use initDataUnsafe for optimistic UI rendering
  • send initData to the backend on app startup
  • wait for the server to validate before enabling sensitive actions

Sensitive actions include:

  • creating orders
  • changing account settings
  • transferring points or credits
  • reading private data
  • issuing privileged API calls

Replay attacks: signature validation is not enough

A valid signature proves the payload came from Telegram, but it does not by itself prove freshness. An attacker who captures a valid initData string may attempt to replay it.

To reduce replay risk:

  • enforce a maximum age for auth_date
  • create short-lived application sessions after verification
  • bind your own session token to the verified Telegram user
  • rotate or invalidate server sessions on logout
  • log repeated verifications from suspicious IPs or devices

A practical freshness window is typically measured in minutes or hours depending on UX. If your app requires long-lived sessions, do not keep reusing the original init data as the sole proof of identity. Use it once, then mint your own session credential.

Protect the login boundary, not just the API

Many Telegram Mini App vulnerabilities appear at the first authenticated endpoint. Developers verify the payload once, then assume every later request is safe because it came from the same browser.

That is only safe if you issue and validate your own session token after the initial Telegram verification.

Recommended architecture:

  • Telegram initData is a bootstrap credential
  • your backend verifies it and creates a session
  • subsequent API requests use your session mechanism
  • the Telegram payload is not re-parsed on every request unless needed for re-authentication

This reduces complexity and prevents accidental trust in front-end state.

Common mistakes that lead to account takeover

Accepting user.id from the client

This is the most obvious flaw and still one of the most common. If the client sends a user ID and the server accepts it without validating Telegram’s signature, identity is forgeable.

Verifying only part of the payload

Checking auth_date but not the hash, or checking the hash but not freshness, leaves room for abuse.

Using unsafe parsing logic

Query-string parsing bugs are frequent. Some frameworks coerce repeated parameters, transform + and %20 differently, or sort parameters implicitly. Use a parser that preserves raw values.

Logging full init data

If you log the entire init data string in plaintext, you may be storing reusable authentication material. Logs are often copied to third-party systems and retained longer than you expect.

Trusting frontend role flags

If your client says is_admin=true, that means nothing unless the server derived that role from a trusted source.

Reusing the init payload as an API credential

The init payload is meant for startup verification, not as a permanent bearer token.

What to store after verification

After the backend verifies the payload, store only the minimum useful identity data:

  • Telegram user ID
  • display name if needed
  • username if present
  • avatar file reference if needed
  • auth timestamp

Do not persist the raw hash or the full init data unless you have a very specific auditing requirement and a secure storage design.

If you need to reproduce a login event later, store a hashed or redacted audit record rather than the full credential.

Session design recommendations

A secure Mini App usually needs two layers:

Layer 1: Telegram proof

Use the signed init data once to prove the user is who they claim to be.

Layer 2: Application session

Issue your own session token or server-side session after verification.

Good properties of the app session:

  • short or moderate lifetime
  • revocable server-side
  • bound to the verified Telegram user
  • optionally tied to device or WebView metadata

Do not rely on the Telegram payload as your long-term auth mechanism.

Defense in depth for privileged actions

Even with correct init data verification, high-risk operations deserve extra checks.

For example:

  • require re-authentication for payouts or destructive actions
  • verify permissions on the server for every sensitive endpoint
  • check business rules independently of the client
  • rate-limit transaction creation and mutation endpoints
  • audit actions by verified Telegram user ID

Security should not depend on the frontend’s visibility into permissions.

Testing your verification code

A good Mini App security review should include negative tests, not just happy-path tests.

Test cases to include:

  • modified user.id
  • altered auth_date
  • changed parameter ordering
  • missing hash
  • stale payloads
  • repeated payload replay
  • malformed query strings
  • Unicode and escaping edge cases
  • mismatched constant-time compare behavior

If possible, generate a known-good payload in a test fixture and compare your implementation against it in multiple languages.

Operational hardening

Beyond signature validation, protect the surrounding system:

  • serve the app and API over HTTPS only
  • set strict CORS rules for your frontend origin
  • avoid exposing admin endpoints to the Mini App context unless necessary
  • secure bot token storage with environment secrets or a secret manager
  • rotate bot tokens if there is any suspicion of leakage
  • monitor anomalous login rates and user-agent patterns

A bot token leak is serious because it can undermine any server-side verification that depends on it.

A practical security checklist

Before shipping a Telegram Mini App, confirm that:

  • the backend verifies raw initData
  • the signature comparison is constant-time
  • auth_date freshness is enforced
  • the bot token never reaches the browser
  • initDataUnsafe is used only for non-security UI
  • sensitive actions require a validated session
  • replay protection is in place
  • logs do not store secrets in full
  • every privileged endpoint performs server-side authorization

If any item is missing, the app is likely trusting the client too much.

Final takeaway

The most important security principle for Telegram Mini Apps is simple: the browser is not an identity provider. Telegram gives you signed startup data so your backend can prove the user’s identity, but that proof only exists if you validate it correctly and then move to your own session model.

If you implement init data verification carefully, keep the client untrusted, and treat the Telegram payload as a bootstrap credential rather than a session token, you eliminate the most common and most damaging Mini App auth bugs.

← All posts