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 withuser,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-colorand others). - It can return data to the bot via
sendData()— the bot will receiveweb_app_datainMessage.
Opening Mini App
Three main ways:
- Inline button — in the bot's message (
reply_markup.inline_keyboard). - Reply button — in the keyboard below the input field (
KeyboardButton.web_app). - 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
- User presses a button → Telegram opens the WebView.
- The app loads, calls
Telegram.WebApp.ready()— the splash screen disappears. - The app reads
initData, sends it to its backend for verification (see the next page). - User interacts with the UI.
- The app calls
Telegram.WebApp.sendData(JSON.stringify({...}))andclose(). - The bot receives
Messagewith fieldweb_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.