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.