Skip to content
✦ AddThisBot
Mini Apps · for users and builders

Telegram Mini Apps: full apps inside a chat.

A Mini App is a web app that opens inside Telegram. No install, no new account. Learn how to open one, what it can see, and how to build your own.

For users

Open a Mini App in one tap.

Mini Apps start from a bot. Telegram supports seven ways to launch them. You will meet most of them without noticing.

01

Launch app button

A bot with a main Mini App shows a Launch app button on its profile.

02

Menu button

The button next to the message field in a bot chat can open a Mini App.

03

Button under a message

A bot sends a message with an inline button that opens the app.

04

Keyboard button

A custom keyboard button opens the app. It can send data back to the chat.

05

Inline mode

Type @botname in any chat and open the app from the results.

06

Direct link

A t.me link with startapp opens the app right in the current chat.

07

Attachment menu

Some apps sit in the paperclip menu. Today this is for major advertisers only.

From our fleet

🧰 Tiny Telegram Tools

The hub bot that opens the tools in one menu.

Open @TinyTelegramToolsBot →

What a Mini App sees

  • Your Telegram user ID, name, username and language
  • Your theme colors, so it can match day or night mode
  • The chat type, when it opens from a link in a chat
  • Your phone number, only if you tap Share in a popup
  • Your chats and messages

Chat bot or Mini App?

Chat botMini App
Best forQuick jobs: a reminder, a draw, a pollRich screens: catalogs, games, editors
How you use itType a command or tap a button in chatTap to open a web page inside Telegram
Works in groupsYes, everyone sees the bot's repliesOpens per person; direct links know the chat
Build withBot API and a webhookHTML, CSS, JS + telegram-web-app.js + a bot
PaymentssendInvoice in StarscreateInvoiceLink + openInvoice in Stars

Most of our 35 bots are chat bots: small jobs go faster with one command in the chat. Browse them →

For builders

Mini App tutorial: build one in five steps.

A Mini App is a normal web page plus Telegram's script. The hard part is trust: your server must check that the data comes from Telegram.

Step 1

Write the page

Load telegram-web-app.js in the head, before other scripts. Then window.Telegram.WebApp is ready. Call ready() and expand(). Style with --tg-theme-* variables so the app matches the user's theme.

index.html
<!doctype html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <!-- Load before your own scripts -->
  <script src="https://telegram.org/js/telegram-web-app.js"></script>
  <style>
    body { background: var(--tg-theme-bg-color); color: var(--tg-theme-text-color); }
    button { background: var(--tg-theme-button-color); color: var(--tg-theme-button-text-color); }
  </style>
</head>
<body>
  <h1 id="hi">Hello</h1>
  <button id="buy">Unlock Pro ⭐ 150</button>
  <script type="module">
    const app = window.Telegram.WebApp;
    app.ready();   // hide the loading placeholder
    app.expand();  // use the full height

    // initDataUnsafe is for display only. Never trust it on the server.
    document.getElementById("hi").textContent = `Hello, ${app.initDataUnsafe.user?.first_name ?? "friend"}`;

    document.getElementById("buy").onclick = async () => {
      // Your server checks initData, then returns a createInvoiceLink URL.
      const res = await fetch("/api/invoice", { method: "POST", headers: { "X-Init-Data": app.initData } });
      const { url } = await res.json();
      app.openInvoice(url, (status) => {
        if (status === "paid") app.showAlert("Pro is on. Thank you!");
      });
    };
  </script>
</body>
</html>

Step 2

Host it and connect it to a bot

Host the page on HTTPS: Cloudflare Pages or a Worker works well. Then connect it. In @BotFather, set the menu button or the bot's main Mini App. Or set the menu button with the Bot API.

To share it, use a direct link: t.me/yourbot?startapp=ref42. The value arrives in start_param.

terminal
# Open your Mini App from the bot's menu button (all users).
# Or use @BotFather: Bot Settings > Menu Button.
curl "https://api.telegram.org/bot$BOT_TOKEN/setChatMenuButton" \
  -H "content-type: application/json" \
  -d '{"menu_button":{"type":"web_app","text":"Open app","web_app":{"url":"https://app.example.com"}}}'

Step 3

Validate initData with HMAC-SHA256

Every launch gives your page an initData query string with a hash. Your server rebuilds the hash from the bot token. If it matches, the data came from Telegram.

validate.ts — Cloudflare Worker
// validate.ts — check Telegram.WebApp.initData on a Cloudflare Worker (Web Crypto, no deps)
const enc = new TextEncoder();

async function hmac(key: BufferSource, msg: string): Promise<ArrayBuffer> {
  const k = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
  return crypto.subtle.sign("HMAC", k, enc.encode(msg));
}
const hex = (b: ArrayBuffer) => [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, "0")).join("");

export async function validateInitData(initData: string, botToken: string, maxAgeSec = 3600) {
  const params = new URLSearchParams(initData);
  const hash = params.get("hash");
  if (!hash) return null;
  params.delete("hash");

  // data-check-string: every other field, sorted by key, "key=value", joined by "\n"
  const dataCheckString = [...params.entries()]
    .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
    .map(([k, v]) => `${k}=${v}`)
    .join("\n");

  const secretKey = await hmac(enc.encode("WebAppData"), botToken); // key = "WebAppData"
  const expected = hex(await hmac(secretKey, dataCheckString));

  // Constant-time compare
  if (expected.length !== hash.length) return null;
  let diff = 0;
  for (let i = 0; i < hash.length; i++) diff |= expected.charCodeAt(i) ^ hash.charCodeAt(i);
  if (diff !== 0) return null;

  const authDate = Number(params.get("auth_date"));
  if (!authDate || Date.now() / 1000 - authDate > maxAgeSec) return null; // too old

  return { user: JSON.parse(params.get("user") ?? "null"), startParam: params.get("start_param") };
}

🔒 Runs only in your browser. It never asks for your bot token. Never paste a token into a website.

Fields (5)

query_id
AAF0aWxlLXNhbXBsZQ
user
{"id":123456789,"first_name":"Ana","username":"ana_sample","language_code":"en","allows_write_to_pm":true}
auth_date
1793000000
signature
c2FtcGxlLXNpZ25hdHVyZS1ub3QtcmVhbA
hash
5d1f0c2a7b9e4f3a8c6d2e1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a19

auth_date: 2 days ago. Reject old data on your server.

{
  "id": 123456789,
  "first_name": "Ana",
  "username": "ana_sample",
  "language_code": "en",
  "allows_write_to_pm": true
}

data-check-string

All fields except hash, sorted by key, joined with a line feed.

auth_date=1793000000
query_id=AAF0aWxlLXNhbXBsZQ
signature=c2FtcGxlLXNpZ25hdHVyZS1ub3QtcmVhbA
user={"id":123456789,"first_name":"Ana","username":"ana_sample","language_code":"en","allows_write_to_pm":true}

Your server computes HMAC_SHA256(key="WebAppData", msg=bot_token), then signs this string with it. The hex result must equal hash (5d1f0c2a7b9e…).

Step 4

Take payment in Stars

Digital goods in Mini Apps must be paid in Telegram Stars. Your server calls createInvoiceLink with currency XTR. The app calls openInvoice(url, callback). The callback gets the status, for example paid.

Still deliver from the bot's successful_payment update. The callback is a UI signal, not proof of payment.

⭐ Read the Stars guide
server: create the invoice link
// A link you can open from a Mini App, a button or a web page.
const url = await tg<string>(env, "createInvoiceLink", {
  title: "Pro",
  description: "Unlock Pro. One payment.",
  payload: `pro:${userId}`,
  provider_token: "",
  currency: "XTR",
  prices: [{ label: "Pro", amount: 150 }],
});
// url is an invoice link. Put it in a button, or open it in a Mini App.

Step 5

Test, then launch

Telegram's test environment is a separate set of accounts. There, your Mini App may use plain HTTP links. Go through this list before launch.

  • HTTPS onlyServe the app over HTTPS. The test environment also allows plain HTTP for testing.
  • Load telegram-web-app.js firstPut the script in <head> before your own scripts.
  • Call ready() earlyIt hides the loading placeholder as soon as your UI can show.
  • Use theme variablesRead --tg-theme-* CSS variables so day, night and custom themes look right.
  • Validate initData on the serverHMAC-SHA256 with your bot token. Check auth_date. Never trust initDataUnsafe.
  • Sell digital goods in StarsUse createInvoiceLink and openInvoice. Other currencies are for physical goods only.
  • Support and termsGive users a clear way to reach support and read your terms, and handle /paysupport in the bot.
  • Test on phone and desktopCheck small screens, the keyboard, the back button and slow networks.

Mini Apps FAQ

What is a Telegram Mini App?

A web app that opens inside Telegram from a bot. It is built with HTML, CSS and JavaScript, gets the user's Telegram theme, and can take payment in Telegram Stars.

Do I need to install a Mini App?

No. You open it from a bot: a Launch app button, the menu button, a button under a message or a link. There is nothing to install and no new account.

What data does a Mini App get about me?

Basic profile data such as your user ID, name, username and language. It asks you before it gets your phone number. It does not get your chats.

How does the server know the data is from Telegram?

It checks the hash in initData. The key is HMAC-SHA256 of the bot token with the string WebAppData as the key. The server signs the sorted data-check-string with it and compares the hex result to hash.

Can a Mini App take payments?

Yes. For digital goods it must use Telegram Stars. The server creates an invoice link with createInvoiceLink and the app opens it with Telegram.WebApp.openInvoice.

Source: core.telegram.org/bots/webapps, checked on 2026-10-28. AddThisBot is not affiliated with Telegram.

For builders

Want a Mini App for your bot?

Two ways to work with us. Pick the one that fits.

Questions first? Message us in Telegram.