Building where the users already are: Telegram Mini Apps
Published March 20, 2026 ยท 5 min read
The distribution problem
Here's the thing about building for Central Asia: your users are already in Telegram. Not "some users" โ basically everyone. Telegram penetration here is unlike anywhere else I know of. So when I was thinking about how to distribute BookUp's customer-facing booking UI, the answer was kind of obvious.
Why convince someone to download an app or remember a URL when they're going to be in Telegram anyway?
What we actually built
For BookUp, the Telegram Mini App is the main customer interface. Someone wants to book at a barbershop โ they open the bot, the mini app loads inside Telegram, they pick a time, done. No new app to install. No login โ Telegram handles identity.
MyPolis uses a similar approach for a civic platform. The matchmaking app I built also ran as a TMA. Same reasoning each time: meet users where they are.
The auth flow is actually clever
The way Telegram passes user identity to your mini app is interesting. When the app loads, Telegram injects a initData string into the JavaScript context. This contains user info (ID, name, username) and a hash.
To verify it's legitimate and not someone crafting a fake payload, you validate the HMAC:
import crypto from 'crypto';
function verifyInitData(initData: string, botToken: string): boolean {
const params = new URLSearchParams(initData);
const hash = params.get('hash');
params.delete('hash');
const dataCheckString = [...params.entries()]
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}=${v}`)
.join('\n');
const secretKey = crypto
.createHmac('sha256', 'WebAppData')
.update(botToken)
.digest();
const expectedHash = crypto
.createHmac('sha256', secretKey)
.update(dataCheckString)
.digest('hex');
return hash === expectedHash;
}Once you verify this on your backend, you can trust the user identity and issue your own session token. It's genuinely clean once you understand it.
The quirks that will catch you
The back button is fake. Telegram provides a "back button" in the header, but it's not a real browser back button โ it's an event you have to handle in JavaScript. If you build a single-page app with react-router expecting normal navigation, you'll need to wire up the Telegram back button event to your router. Easy once you know, confusing before you do.
The keyboard on iOS is... special. When a text input gets focus on iOS inside a Telegram mini app, the viewport shifts in ways that are different from a normal Safari web view. Lots of "input is hidden behind the keyboard" bugs. I've spent more time on this than I'd like to admit.
Layout viewport vs. visual viewport. Telegram exposes window.Telegram.WebApp.viewportHeight and viewportStableHeight. You want viewportStableHeight for layout โ viewportHeight changes as the keyboard opens and causes layout jumps. Took me a while to figure that one out.
Theme variables are a gift. Telegram passes CSS variables for the user's current theme (dark/light, accent colors, background). Using these makes your app feel native to the Telegram skin the user has. Worth doing.
Honest pros and cons
Pros: near-zero distribution friction, no App Store review, instant updates, identity for free, users are already there.
Cons: you're building inside someone else's container. Telegram can change the API. The WebView has quirks that browsers don't. Debugging is harder. You're dependent on Telegram's infrastructure.
For the markets I'm building for, the pros win clearly. If you're building consumer software for a Telegram-heavy region, think hard before building a separate app instead.