WeMAIdeStart a project ↗

WEMAIDE / INSIGHTS / ENGINEERING

Telegram Mini App authentication: validate initData, then create your application session

Follow Telegram Mini App initData from client to backend. Includes an offline Node.js HMAC example, freshness checks, tampered-data tests and session boundaries.

Engineering12 min read
Telegram interface connects to application-owned backend responsibilities

For a Telegram Mini App, send the raw Telegram.WebApp.initData string to your backend, validate it using the documented Telegram verification route, apply a freshness policy and then map the verified Telegram identity to an application account. Create your own session and check record permissions on every protected request. Client-side initDataUnsafe is not a trusted login result.

This guide covers a user-bearing Mini App launch and a bot-owned backend using HMAC validation. It is separate from Telegram Login for a website, MTProto client authorisation and Telegram Gateway verification. The included reference runs locally on synthetic data; it is not a deployed login service or a security audit.

Choose the authentication flow you are actually building

Several Telegram features share the word authentication
SituationRelevant starting pointDo not assume
A web interface opened as a Telegram Mini AppValidate raw Mini App initData on the serverA browser-supplied user object is already trusted
Sign in to an ordinary website with TelegramTelegram's supported website login flowIts payload and verifier are interchangeable with Mini App initData
Build a Telegram client using a user accountTelegram API user authorisationA Bot API token creates a client account session

Keep the raw string intact until server-side verification

Read Telegram.WebApp.initData in the Mini App and send that raw query string over HTTPS to the intended authentication endpoint. Do not rebuild it from initDataUnsafe, change a serialized user object or independently trust the user_id in a request body. Validate the received representation according to Telegram's algorithm. Treat initialization data as a credential-like value: do not place it in analytics, error screenshots or a public URL.

Raw Telegram initData crosses a server validation boundary before an application session and per-record permission checks
An authenticated Telegram identity is an input to the application's session. It does not supply an admin role or permission to read every order.Open full-size diagram (SVG) ↗

Verify the HMAC and freshness before trusting the user

  1. Parse the query fields without accepting duplicate keys. Extract the received hash and build the sorted data-check string from the other received fields.
  2. Derive the secret key with HMAC-SHA-256 using WebAppData as the key and the bot token as the message.
  3. Compute the data-check string's HMAC with that derived key and compare the fixed-size hash bytes using a constant-time comparison.
  4. Check auth_date against an explicit maximum age and future-clock tolerance, then parse the required user data.
  5. Resolve or create the application's own account under its onboarding policy; issue a session through the existing server authentication stack.

Keep the bot token on the server in the supported secret store. The supplied reference implements the bot-owned HMAC route. Telegram also documents an Ed25519 route for third parties that do not receive the bot token. Its data-check string is different: do not combine the two algorithms or copy exclusions from one into the other.

For the HMAC route, the reference removes hash and includes the other received fields, including signature if present. It does not reserialize JSON before hashing. The separate third-party route excludes both hash and signature and includes the documented bot-id prefix. Use a maintained verifier appropriate to the chosen route in a production integration and check its current contract.

Run a synthetic validation example without a Telegram account

The downloadable Node.js 22+ module has no dependencies and makes no network calls. Its demo signs invented data with a clearly synthetic token and verifies the result. It also rejects altered, expired and duplicate-field inputs. The example's five-minute maximum age, 30-second future tolerance and 16 KiB input cap are local teaching policies, not limits imposed by Telegram.

node mini-app-initdata-reference.mjs --demo

Expected summary:
mode: offline-synthetic
valid: accepted
tampered: rejected
expired: rejected
duplicate: rejected
sessionsCreated: 0
Download the server-only initData reference (.mjs) ↓

HMAC and freshness validation plus an offline synthetic demo. No real bot token, session service or Telegram connection included.

Local tests also check a separately assembled HMAC fixture, the wrong token, percent-encoded values, missing user data, invalid hashes, oversized input, unsafe identity shapes and the freshness boundary. This demonstrates the small verifier's behaviour; a real supported Telegram launch and your session endpoint still require integration tests. Do not ship the synthetic fixture signer to a browser as an authentication feature.

A fresh signature is not a one-time login ticket

Freshness reduces the period in which old initialization data is accepted; it does not make a signed payload single-use. Someone who obtains a still-valid payload may be able to replay it. Protect the transport and storage, keep the exchange endpoint bounded and decide how your server handles repeated exchanges. If the product requires one-time consumption, implement that explicitly in shared server state rather than claiming auth_date prevents replay.

Return a session through the application's established authentication mechanism. A cookie-based implementation needs appropriate Secure, HttpOnly and SameSite settings for its actual embedding and navigation context, plus CSRF protection where relevant. Set a session lifetime, revocation path and renewal policy. Never choose an admin role from start_param, a client field or a model response.

Authorisation happens again when an order is opened

Illustrative server flow — not runnable endpoint code
POST /auth/telegram-mini-app
  raw initData → verify HMAC → enforce freshness
  → map verified Telegram ID to local account
  → issue application session through the auth stack

GET /orders/SYN-42
  validate session → load account
  → query order owned by that account or an allowed staff role
  → return only permitted fields

Knowing who the Telegram user is does not establish that they own order SYN-42. Bind protected reads and writes to the authenticated application account. A UI that hides an admin button does not protect the underlying route. Recheck permissions when the record changes, an employee role is removed or a subscription expires.

Handle launch modes and failures deliberately

Expected authentication failures are part of the user flow
ConditionResponseWhat to avoid
Opened in a plain browser without usable initDataExplain the supported Telegram entry point or use a separately designed loginInventing identity from URL fields
Invalid signature or changed fieldsReject authentication; retain a minimal diagnostic categoryFalling back to initDataUnsafe
Expired initialization dataAsk the user to reopen through the supported launch flowIgnoring freshness to make the demo pass
Valid identity, forbidden recordReturn an access denial without revealing another customer's dataTreating all Telegram users as one account

Initialization fields vary with the launch mode. This reference deliberately rejects a launch without a valid user field instead of pretending all entry points provide the same identity. Test the exact launch methods, devices and session behaviour your product supports before release.

Built from the work.WeMAIde designs and ships AI software, apps, Telegram products and automation systems.

FROM THINKING TO PRODUCT

Need something like this built?

Tell us what needs to work. We will come back with the clearest next step.

Discuss it in Telegram ↗