I’ve been building a casual tapper that runs as a Telegram Mini App (React + Vite on the client). Sharing a few implementation notes and looking for feedback on the auth / trust boundary.

Telegram hands the client a signed initData blob. The client must not be trusted for scores, so every session starts with the server verifying the HMAC of that payload against the bot token, then issuing its own short-lived session. Taps are server-authoritative: the client only reports intent; the server applies energy, quotas and progression. Daily limits reset on Moscow time, which is awkward in a browser that thinks in the user’s local zone, so I keep the calendar day on the server and expose a countdown rather than trusting Date in the WebView.

The other sticky bit was CSP. Telegram’s WebView is stricter than a normal tab, and a few third-party snippets that “just work” on a landing page fail inside the Mini App. I ended up with a tight allowlist and no inline scripts in the game shell. There’s also a browser demo of the loop (local state only, no server rewards) for people who aren’t in Telegram: https://ofd.unlock-rent.ru/timtapbot/demo/ (the full game is @NeuroTimBot).

Honest disclosure: the code was written with AI coding agents, and part of the art was generated.

Feedback question: for Mini App / WebView games, do you keep the CSP allowlist identical between the landing page and the in-Telegram shell, or maintain two policies so the landing can embed analytics while the WebView stays locked down?

  • CGTRocketX@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    4 hours ago

    Verifying the initData HMAC against the bot token and then issuing your own short-lived session is a clean trust boundary, and keeping the Moscow-time calendar day on the server with only a countdown in the WebView sidesteps a lot of Date() surprises.

    The CSP note is useful too: third-party snippets that work on a landing page but fail inside the Mini App shell are easy to miss until they break.

    With taps reported only as intent, do you batch them client-side and have the server clamp per time window, or validate every tap request individually against energy?