mini apps
connect telegram mini apps to a yaebal bot through web app buttons, @yaebal/mini-app's initData validation, and bot api callbacks.
open a mini app
inline keyboards can open a web app url inside telegram. webAppUrl validates https (required in production) and merges deep-linking query params.
import { createBot, InlineKeyboard } from "yaebal";
import { webAppUrl } from "@yaebal/mini-app";
const bot = createBot(process.env.BOT_TOKEN!);
bot.command("app", (ctx) =>
ctx.reply("open the app", {
reply_markup: new InlineKeyboard().webApp("open", webAppUrl("https://app.example.com")),
}),
);validate initData before trusting it
whether the mini app hands data back through a bot command or its own http backend, validate initData first — never trust user/chat/start_param before a validate* call has confirmed the signature. see @yaebal/mini-app for the full HMAC/Ed25519/ Authorization-header surface.
import { createBot } from "yaebal";
import { miniApp } from "@yaebal/mini-app";
const bot = createBot(process.env.BOT_TOKEN!).install(miniApp({ botToken: process.env.BOT_TOKEN! }));
bot.command("check", async (ctx) => {
const initData = ctx.message?.text?.split(" ").slice(1).join(" ") ?? "";
const result = await ctx.miniApp.validate(initData);
if (!result.ok) return ctx.reply("rejected: " + result.reason);
await ctx.reply("hi " + result.data.user?.first_name + "!");
});most mini apps talk to their own web backend instead, via the Authorization: tma header convention:
// your mini app's own http backend — not a bot update
import { validateAuthHeader } from "@yaebal/mini-app";
export default {
async fetch(req: Request) {
const result = await validateAuthHeader(req.headers.get("authorization"), process.env.BOT_TOKEN!);
if (!result.ok) return new Response("unauthorized", { status: 401 });
return new Response("hi " + result.data.user?.first_name);
},
};web app data messages
if the mini app calls Telegram.WebApp.sendData(), it arrives as message.web_app_data — client-controlled, so validate its shape like any other
untrusted input (parseWebAppData in @yaebal/mini-app JSON-parses it for you).
import { createBot } from "yaebal";
const bot = createBot(process.env.BOT_TOKEN!);
bot.on("message:web_app_data", async (ctx) => {
await ctx.reply("mini app sent: " + ctx.message.web_app_data.data);
});security checklist
- validate
initDataon your backend before trusting user identity —ctx.miniApp.validate(HMAC) orctx.miniApp.validateThirdParty(Ed25519, no bot token) from@yaebal/mini-app. initDatahas no built-in expiry —@yaebal/mini-appdefaults to rejecting anything older than 24h (maxAge), guarding against replaying a leaked-but-genuine payload.- do not put bot tokens in the mini app frontend.
- treat
web_app_data.dataas untrusted input. - use short-lived server-side state for checkout or privileged actions.
- log only safe user ids and action names, not full
initDatastrings.
external domains
since july 20, 2026, telegram blocks mini app methods on external origins opened through
links inside the app by default. keep method calls on the original mini app domain.
this browser-side restriction does not change server-side initData validation.
botfather's mini app offers an opt-out that leaves trust in linked sites to the bot developer.
see the bot api 10.2 changelog.