Mini Apps
React and Vue
React/Vue Mini App integration with a PHP backend: passing initData, validation via verifyInitData(), responding via answerWebAppQuery, parsing JSON responses and errors.
Once the Mini App frontend is ready (React or Vue), the main task is to securely pass user actions to the PHP backend. In Chapter Validating initData, we covered the verifyInitData() — it validates the signature and returns an array with user data. In the chapter First request: getMe we have the telegramApi() function for Bot API calls. Here, we will bring everything together.
Request Architecture from SPA to PHP
A Mini App (SPA) never calls the Bot API directly—the bot token must never reach the browser. The workflow:
- React/Vue reads
window.Telegram.WebApp.initData(query string). - Sends a POST request to your PHP endpoint, putting
initDatainto theX-TG-Init-Dataheader. - PHP verifies the signature using
verifyInitData(), getsuser.id,query_id, and your launch parameters. - Executes business logic and, if the Mini App was opened via an inline button, responds using the
answerWebAppQuerymethod—this instantly closes the WebView and sends a message to the chat.
PHP Handler Example (Endpoint for SPA)
File api/miniapp/order.php. We use helper functions from previous chapters without duplicating the HMAC code:
<?php
require_once __DIR__ . '/../../bootstrap.php'; // подключает telegramApi(), verifyInitData()
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Method Not Allowed']);
exit;
}
$headers = getallheaders();
$initData = $headers['X-TG-Init-Data'] ?? '';
if (empty($initData)) {
http_response_code(401);
echo json_encode(['error' => 'Missing X-TG-Init-Data header']);
exit;
}
// 1. Валидация — используем функцию из главы «Проверка initData"
$validated = verifyInitData($initData);
if (!$validated) {
http_response_code(403);
echo json_encode(['error' => 'Invalid initData signature']);
exit;
}
// $validated содержит: ['user' => [...], 'query_id' => '...', 'start_param' => 'ref_123', ...]
$queryId = $validated['query_id'] ?? null;
$userId = $validated['user']['id'] ?? null;
if (!$queryId) {
http_response_code(400);
echo json_encode(['error' => 'query_id absent — Mini App не открыт через inline-кнопку']);
exit;
}
// 2. Читаем тело запроса (product_id, quantity и т.д.)
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$productId = (int)($input['product_id'] ?? 0);
if ($productId <= 0) {
http_response_code(422);
echo json_encode(['error' => 'Invalid product_id']);
exit;
}
// 3. Бизнес-логика: сохраняем заказ, списываем склад и т.д.
// $orderId = createOrder($userId, $productId);
// 4. Отвечаем Telegram через answerWebAppQuery — закрывает Mini App и шлёт сообщение
$answer = telegramApi('answerWebAppQuery', [
'web_app_query_id' => $queryId,
'result' => [
'type' => 'article',
'id' => 'order_' . time(),
'title' => 'Заказ оформлен',
'input_message_content' => [
'message_text' => "✅ Заказ #{$productId} принят в работу. Ожидайте звонка менеджера.",
'parse_mode' => 'HTML'
],
'description' => 'Статус: новый',
'thumb_url' => 'https://example.com/thumb-ok.png',
'thumb_width' => 64,
'thumb_height' => 64
]
]);
// 5. Проверяем ответ Telegram API
if (!($answer['ok'] ?? false)) {
error_log('answerWebAppQuery failed: ' . json_encode($answer));
http_response_code(502);
echo json_encode(['error' => 'Telegram API error', 'details' => $answer]);
exit;
}
echo json_encode(['success' => true, 'order_id' => $orderId ?? null]);
Example of a Successful Telegram API Response
The answerWebAppQuery method returns a SentWebAppMessage object (or true in older versions). The result field contains the message that the user will see in the chat:
{
"ok": true,
"result": {
"message_id": 142,
"date": 1700000123,
"chat": {
"id": 123456789,
"type": "private",
"first_name": "Ivan",
"username": "ivan_p"
},
"via_bot": {
"id": 987654321,
"is_bot": true,
"first_name": "MyShopBot",
"username": "myshopbot"
},
"text": "✅ Заказ #42 принят в работу. Ожидайте звонка менеджера.",
"entities": []
}
}
What to look for: ok=true indicates the request succeeded. result.message_id contains the ID of the sent message (which can later be edited via editMessageText). via_bot confirms that the message was sent on behalf of your bot.
Common Errors
{"ok":false,"error_code":400,"description":"Bad Request: query_id is invalid"}—query_idhas expired (valid for ~24 hours) or the Mini App was not opened via an inline button (missing query_id).{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}— the user blocked the bot; the message will not be delivered.{"ok":false,"error_code":429,"description":"Too Many Requests"}— rate limit exceeded, a retry with a delay is required (see Chapter Production: очередь и retries).
How to Call This Endpoint from React / Vue
A minimal fetch example (common to both frameworks):
async function sendOrder(productId) {
const initData = window.Telegram?.WebApp?.initData || '';
const res = await fetch('/api/miniapp/order.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-TG-Init-Data': initData
},
body: JSON.stringify({ product_id: productId })
});
const data = await res.json();
if (!res.ok) {
console.error('Backend error:', data);
// покажите пользователю data.error
}
// если success — Mini App закроется сам через answerWebAppQuery
}
In React, call it inside a button click handler; in Vue, inside a component method or composable. The key is to pass the X-TG-Init-Data header.
Debugging: What and Where to Check
- PHP logs:
error_log()writes to php-fpm/error.log, where you can see the Telegram API response when errors occur. - Browser DevTools → Network: inspect the request to
/api/miniapp/order.php, check theX-TG-Init-Dataheader and the JSON response. - Telegram Desktop / Web: open the WebView console (Ctrl+Shift+I in Telegram Desktop) to see
console.loglogs from the Mini App. - In terminal:
php api/miniapp/order.phpwon't work (no POST), but you can test it via curl:curl -X POST http://localhost/api/miniapp/order.php \
-H 'Content-Type: application/json' \
-H 'X-TG-Init-Data: query_id=...&user=...&hash=...' \
-d '{"product_id":42}' | jq .
Next: Invoices and Payments