Переклад цього гайду зʼявиться незабаром. Поки що текст доступний російською — перемкніться на RU або читайте нижче. Відкрити російську версію

Mini Apps

React і Vue

Когда фронтенд Mini App готов (React или Vue), главная задача — безопасно передать действия пользователя на PHP-бэкенд. В главе Проверка initData мы разобрали функцию verifyInitData() — она валидирует подпись и возвращает массив с данными пользователя. В главе Первый запрос: getMe у нас есть telegramApi() для вызовов Bot API. Здесь соединим всё вместе.

Архитектура запроса от SPA к PHP

Mini App (SPA) никогда не дергает Bot API напрямую — токен бота не должен попасть в браузер. Схема:

  1. React/Vue читает window.Telegram.WebApp.initData (строка query string).
  2. Отправляет POST на ваш PHP-эндпоинт, кладя initData в заголовок X-TG-Init-Data.
  3. PHP проверяет подпись через verifyInitData(), получает user.id, query_id и ваши параметры запуска.
  4. Выполняет бизнес-логику и, если Mini App открыт через inline-кнопку, отвечает методом answerWebAppQuery — это мгновенно закрывает WebView и шлёт сообщение в чат.

Пример PHP-обработчика (endpoint для SPA)

Файл api/miniapp/order.php. Используем хелперы из прошлых глав — не дублируем HMAC-код:

<?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]);

Пример успешного ответа Telegram API

Метод answerWebAppQuery возвращает объект SentWebAppMessage (или true в старых версиях). В поле result приходит сообщение, которое пользователь увидит в чате:

{
"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": []
}
}

Что смотреть: ok=true — запрос прошёл. В result.message_id — ID отправленного сообщения (можно потом отредактировать через editMessageText). via_bot подтверждает, что сообщение отправлено от имени вашего бота.

Типичные ошибки

  • {"ok":false,"error_code":400,"description":"Bad Request: query_id is invalid"} — query_id устарел (живёт ~24 часа) или Mini App открыт не через inline-кнопку (нет query_id).
  • {"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"} — пользователь заблокировал бота, сообщение не уйдёт.
  • {"ok":false,"error_code":429,"description":"Too Many Requests"} — превышен лимит, нужен retry с задержкой (см. главу Production: очередь и retries).

Как вызвать этот эндпоинт из React / Vue

Минимальный пример на fetch (общий для обоих фреймворков):

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
}

В React вызывайте внутри обработчика кнопки, во Vue — в методе компонента или composable. Главное — передать заголовок X-TG-Init-Data.

Отладка: что и где смотреть

  • PHP-логи: error_log() пишет в php-fpm/error.log — там видно ответ Telegram API при ошибках.
  • Браузер DevTools → Network: смотрите запрос к /api/miniapp/order.php, проверяйте заголовок X-TG-Init-Data и JSON-ответ.
  • Telegram Desktop / Web: откройте консоль WebView (Ctrl+Shift+I в Telegram Desktop) — там видны console.log из Mini App.
  • В терминале: php api/miniapp/order.php не сработает (нет POST), но можно протестировать через 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 .

Дальше: Invoices и Payments