Guide

App security

A mini app runs untrusted web content inside SprinTelex, so the trust model is explicit: the platform guarantees the container, the developer guarantees their own server. This page lists both sides.

Origin binding

The viewer locks navigation to your app's verified domain and its subdomains, over HTTPS only. Any navigation anywhere else is blocked. Links your app opens with SprinTelex.openLink leave the viewer entirely and open in the system browser, where the user sees the real address bar.

The bridge itself is only callable from your registered origin, and only over HTTPS. Plain HTTP app URLs are refused at registration, and the viewer never loads them.

Client chrome you cannot fake

The viewer always renders its own header above your page: your app's name and its verified domain, plus which bot opened it. That chrome is drawn by the client, never by your HTML, so a phishing page inside the viewer cannot impersonate SprinTelex UI or hide which site the user is really on.

Design your pages knowing the header is always there. Never draw your own lookalike header, and never ask for credentials inside the viewer that belong on your own site.

initData: never trust the client

Everything in window.SprinTelex is client data. The signature is what makes it trustworthy, and only your server can check it, because only your server holds the app_secret. Validate on every request that acts on a user, not just at session creation: recompute the HMAC chain, compare in constant time, and enforce the auth_date freshness window (30 minutes normally, 24 hours absolute maximum). initData carries no nonce, so that TTL is your only replay defense.

See the Building a mini app guide for the exact algorithm, the worked test vector, and copy-paste validators in Python and Node.js.

app_secret vs bot token

Your bot has two separate secrets because they protect two different things:

Bot tokenapp_secret
ProvesYour server may act as the bot (Bot API)A user really opened your app (initData)
Lives onYour backend onlyYour backend only (never in page JS)
If leakedAttacker controls the bot: rotate itAttacker can forge app sessions: regenerate it

The separation matters because your web server needs the app_secret to validate sessions, and web servers get compromised more often than locked-down bot backends. A leaked app_secret lets an attacker forge app sessions, but it grants zero Bot API access: they cannot send messages as your bot or read its updates. Regenerating the secret instantly invalidates every previously signed payload.

The 4KB sendData limit

sendData payloads are capped at 4KB, enforced on the client (which shows a "Too much data" alert) and again on the server (400 data must be at most 4KB). Keep app-to-bot messages small by design: send choices and references, not documents. If you need to move bulk data, have your server fetch it directly using the validated session.

Hardened container

The viewer runs your page with file access disabled, geolocation disabled, media capture gated behind user action, third-party and shared cookies disabled, and no generic script gateway: each bridge member is a single-purpose handler whose parameters are validated as attacker input. Your page gets its own app-scoped storage and nothing else.

What the platform guarantees vs what you must do

Platform guaranteesYou must do
Navigation locked to your verified domain, HTTPS onlyKeep the verified domain yours; re-verify after moving
App name plus domain always visible in client chromeNever draw a fake header or hide where the user is
initData signed with your per-bot app_secretValidate the signature server side on every sensitive request
sendData authenticated as the opener, capped at 4KBTreat the payload as user input; validate it like any other
Secret shown once, regenerable, never re-readableStore it as an env var; rotate it if it ever leaks
Suspended or deleted bots and apps cannot launchHandle launch failures gracefully in your page
The classic failure is validating initData once at login and then trusting a session cookie you mint yourself without binding it to the validated payload. Bind your session to the scoped user id from the validated initData, and re-validate on any request that moves value or changes account state.