Mini Apps

What is a Mini App

What is Telegram Mini App, how to open it via inline- and reply buttons, how bots receive data through web_app_data, and PHP examples with telegramApi().

Mini App — this is a web application that runs inside Telegram using a native WebView. The user does not leave the messenger: the interface opens over the chat, has access to theme (light/dark), can request user data and send results back to the bot.

How it works

The bot sends a message containing InlineKeyboardButton with the field web_app set to your app's URL. When Telegram opens, it launches the WebView, loads the page, and passes initial data via window.Telegram.WebApp.initData.

Inside the Mini App, there is a JavaScript SDK (@twa-dev/sdk or CDN script) that provides methods: ready(), expand(), close(), sendData(), working with MainButton, BackButton, HapticFeedback and others.

Differences from a regular website

  • It launches only from Telegram — there is no direct browser access via URL.
  • It receives initData — a signed string with user, chat, query_id, auth_date, hash. This is the only way to identify the user on the backend.
  • It has access to the Telegram theme via CSS variables (--tg-theme-bg-color, --tg-theme-text-color and others).
  • It can return data to the bot via sendData() — the bot will receive web_app_data in Message.

Opening Mini App

Three main ways:

  1. Inline button — in the bot's message (reply_markup.inline_keyboard).
  2. Reply button — in the keyboard below the input field (KeyboardButton.web_app).
  3. Direct link — https://t.me/<bot_username>/<app_name> (configured in BotFather → Menu Button).

Example: sending a button via telegramApi()

In the main page we created the function telegramApi() — here we use it. It already knows how to encode JSON, check HTTP status code and field ok.

<?php

$token = getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
throw new RuntimeException('TELEGRAM_BOT_TOKEN not set');
}

function telegramApi(string $method, array $params = []): array
{
$ch = curl_init("https://api.telegram.org/bot{$token}/{$method}");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$res = curl_exec($ch);
if ($res === false) {
throw new RuntimeException('cURL error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode !== 200) {
throw new RuntimeException("HTTP {$httpCode}: {$res}");
}

$data = json_decode($res, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('Invalid JSON: ' . json_last_error_msg());
}
if (!($data['ok'] ?? false)) {
throw new RuntimeException("API error: " . ($data['description'] ?? 'unknown'));
}
return $data['result'];
}

$chatId = 123456789; // замените на реальный chat_id
$url = 'https://example.com/miniapp';

$result = telegramApi('sendMessage', [
'chat_id' => $chatId,
'text' => 'Нажмите кнопку ниже, чтобы открыть Mini App:',
'reply_markup' => [
'inline_keyboard' => [[
['text' => 'Открыть Mini App', 'web_app' => ['url' => $url]]
]]
]
]);

// Посмотрим, что вернул API
print_r($result);

Example response API (sendMessage)

{
"ok": true,
"result": {
"message_id": 42,
"from": {
"id": 987654321,
"is_bot": true,
"first_name": "MyBot",
"username": "mybot"
},
"chat": {
"id": 123456789,
"first_name": "Ivan",
"type": "private"
},
"date": 1700000000,
"text": "Нажмите кнопку ниже, чтобы открыть Mini App:",
"reply_markup": {
"inline_keyboard": [[
{
"text": "Открыть Mini App",
"web_app": {"url": "https://example.com/miniapp"}
}
]]
}
}
}

The field result is an object Message. Save message_id if you plan to edit or delete this message later (see chapter Редактирование и удаление). In the terminal print_r($result) will show the array structure — convenient for debugging.

Example: Reply button with web_app

Sometimes it is more convenient to attach a button to the keyboard below the input field — it does not disappear after pressing.

<?php

$result = telegramApi('sendMessage', [
'chat_id' => $chatId,
'text' => 'Откройте приложение из меню:',
'reply_markup' => [
'keyboard' => [[
['text' => '🛒 Каталог', 'web_app' => ['url' => 'https://example.com/shop']]
]],
'resize_keyboard' => true,
'one_time_keyboard' => false
]
]);

var_dump($result);

var_dump will show string types and lengths — useful when you need to make sure message_id arrived as int, not string.

Mini App lifecycle

  1. User presses a button → Telegram opens the WebView.
  2. The app loads, calls Telegram.WebApp.ready() — the splash screen disappears.
  3. The app reads initData, sends it to its backend for verification (see the next page).
  4. User interacts with the UI.
  5. The app calls Telegram.WebApp.sendData(JSON.stringify({...})) and close().
  6. The bot receives Message with field web_app_data — processes the result.

How the bot gets data from Mini App

When the user presses the "Ready" button in the app and it calls sendData(), Telegram delivers a normal message to the bot with field web_app_data:

{
"update_id": 123456789,
"message": {
"message_id": 43,
"from": {"id": 123456789, "first_name": "Ivan"},
"chat": {"id": 123456789, "type": "private"},
"date": 1700000005,
"web_app_data": {
"button_text": "Открыть Mini App",
"data": "{\"action\":\"checkout\",\"items\":[1,2,3]}"
}
}
}

The field web_app_data.data is the string passed by sendData(). Usually this is JSON. In the PHP handler (see chapter getUpdates и update_id) parse it like this:

<?php

// внутри processUpdate($update) из главы Long polling
if (isset($update['message']['web_app_data'])) {
$raw = $update['message']['web_app_data']['data'];
$payload = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE) {
// невалидный JSON от приложения
telegramApi('sendMessage', [
'chat_id' => $update['message']['chat']['id'],
'text' => 'Ошибка: неверные данные от приложения'
]);
return;
}
// $payload теперь ассоциативный массив: ['action' => 'checkout', 'items' => [1,2,3]]
// ... ваша бизнес-логика ...
telegramApi('sendMessage', [
'chat_id' => $update['message']['chat']['id'],
'text' => 'Заказ принят: ' . json_encode($payload, JSON_UNESCAPED_UNICODE)
]);
}

Configuring Menu Button (chat menu button)

To open the Mini App by clicking on the bot's name in the chat header, configure the Menu Button via BotFather or method setChatMenuButton:

<?php

telegramApi('setChatMenuButton', [
'chat_id' => $chatId, // можно не указывать — тогда для всех чатов
'menu_button' => [
'type' => 'web_app',
'text' => 'Открыть приложение',
'web_app' => ['url' => 'https://example.com/miniapp']
]
]);

API Response:

{
"ok": true,
"result": true
}

The field result here is simply true — meaning the configuration was applied. You can check the current button using method getChatMenuButton.

Mini Apps are suitable for catalogs, forms, payments, games, dashboards — everything where a rich UI is needed that native keyboards lack. The main thing is not to forget to verify initData on the backend, otherwise anyone could spoof the request.

Дальше: Проверка initData

"}