Hosting a Telegram Mini App — HTTPS and the Validation Step Everyone Skips
Hosting a Telegram Mini App comes down to two things: serve it over HTTPS with a valid certificate, and validate initData on your server before you trust a single thing it says. The first is infrastructure — an HTTPS port binding gets you a certificate issued and renewed automatically, with no domain to buy. The second is the part tutorials skip, and it's the one that matters: initData arrives from the client, so without a signature check anyone can claim to be any Telegram user by sending you a different user ID.
What a Mini App actually is
A Mini App is an ordinary web page that Telegram loads in a webview inside the chat client, launched from a bot's button or a direct link. There's nothing exotic in the hosting: it's HTML, CSS, and JavaScript, usually with a backend behind it.
Which means you're really running two things:
- The web app — the page Telegram embeds, and whatever API it calls.
- The bot — the thing that shows the button that launches it.
They can be one application or two. More on that below.
The hosting requirements
HTTPS with a valid certificate. Telegram loads Mini Apps over secure connections only, and a self-signed certificate won't do (Mini App launch guide). This is the part that's genuinely free here: add an HTTPS port binding for whatever port your app listens on, and it gets a subdomain with a certificate that's issued and renewed for you. No domain purchase, no Let's Encrypt cron, no reverse proxy.
Load quickly. Mini Apps are expected to be usable within a few seconds of opening, and a webview in a chat client is not a forgiving place to load a 4 MB bundle. Keep the initial payload small and defer anything you don't need for the first screen.
Bind to 0.0.0.0. Same trap as any containerised web app — a server bound to localhost is unreachable from outside the container, and it fails as a successful deploy with a 502 rather than a build error. More on that here.
Validate initData — this is the important part
When Telegram opens your Mini App it passes initData: a string containing the user's ID, name, language, the chat context, an auth date, and a hash.
It is tempting to read initData.user.id in JavaScript and send it to your backend as "who this is." Don't. It arrives in the browser, which means it arrives from the user, which means it can be anything they want it to be. Your API would happily accept a request claiming to be someone else's account.
Telegram's own documentation is blunt about this — its Mini Apps guide carries an explicit warning to validate the data before your bot's server uses it (Telegram Mini Apps).
The check is a signature verification: your server rebuilds a data-check-string from the received fields sorted alphabetically, computes an HMAC-SHA-256 using a key derived from your bot token, and compares it to the hash Telegram sent. If they match, the data genuinely came from Telegram and hasn't been altered. The exact construction is specified in Telegram's guide, and it's worth following it rather than a blog snippet — the field ordering and the key derivation are both easy to get subtly wrong.
In practice, use a library. aiogram ships helpers for it, and there are small dedicated packages for Python and Node that implement the check. Rolling your own HMAC is exactly the kind of code where a subtle mistake looks like it works.
Three rules once you're validating:
- Do it server-side, every request. Client-side validation proves nothing — the client is the untrusted party.
- Check
auth_datefreshness. A valid signature stays valid forever; rejectinitDataolder than a sensible window so a captured payload can't be replayed indefinitely. - Never send your bot token to the browser. It's the key to the whole signature scheme. It belongs in a server-side environment variable and nowhere else.
One app or two?
Both work; the trade is the same as any split.
One app serving the static files and the API, with the bot running in the same process, is simplest — one deploy, one set of config, one place to look at logs. Good for a Mini App that's mostly a front-end for one bot.
Two apps — bot in one, web app plus API in the other — means you can redeploy the web side without disconnecting the bot from Telegram's gateway, which matters if you're iterating on UI. It costs nothing extra in billing: usage is aggregated across applications, so the app count doesn't change the bill, only the resources you actually use.
If you go with two, they'll need to share state. That's a database rather than memory — the two containers can't see each other's variables.
Sizing it
A Mini App backend is a good fit for a small container: it serves a bundle and some JSON, and spends most of its time idle between opens. The things that would push it out of that range are the usual suspects — loading a dataset at startup, or generating images on the fly.
Since the front-end is static files, consider whether your backend needs to serve them at all. Letting your web framework serve a built bundle is fine at this size and keeps everything in one place.
FAQ
Do Telegram Mini Apps require HTTPS?
Yes — Telegram loads them over secure connections with a valid certificate. An HTTPS port binding provides one automatically, so there's no certificate to buy or renew.
Do I need to buy a domain for a Mini App?
No. The app gets a subdomain with a valid certificate, which is enough for Telegram to load it.
What is initData and why must I validate it?
It's the signed payload Telegram passes to your Mini App containing the user and chat context. It reaches you via the browser, so without verifying its HMAC signature on your server, a user can send any user ID they like.
Can I just read the user ID in JavaScript?
Only for display. Never for authorisation — anything the client sends can be forged. Authorise on the server, after validating the signature.
Should the bot and the Mini App be one app or two?
One is simpler and fine for most cases. Two lets you redeploy the web side without dropping the bot's gateway connection, and it doesn't cost more since billing is by resource usage, not app count.
Why does my Mini App show a blank screen?
Usually the page isn't reachable — check the app is bound to 0.0.0.0 on the port you exposed — or the bundle is too slow to load inside the webview.
Wrapping up
The hosting side of a Mini App is unremarkable, which is the good news: a small web app, an HTTPS binding, a certificate you didn't have to think about. The part worth your attention is the one that isn't visible when it's broken — an unvalidated initData gives you an app that works perfectly for every honest user and hands accounts to anyone who edits a request. Validate the signature server-side, check the timestamp, keep the token off the client, and the rest is just a web page.