Під час надсилання даних із Telegram Mini App на ваш бекенд єдиним джерелом правди є рядок initData. Його формує клієнтський додаток Telegram і підписує закритим ключом бота. Якщо валідувати дані некоректно або довіряти параметрам initDataUnsafe із клієнтського JavaScript, зловмисник зможе підробити user.id, обійти авторизацію або виконати дії від імені будь-якого користувача.
У цьому матеріалі ми розберемо безпечний алгоритм перевірки initData на PHP, напишемо ізольовану функцію валідації та розглянемо надсилання зворотного виклику в Telegram API з дотриманням стандартів обробки помилок.
Анатомія рядка initData та алгоритм перевірки
Рядок initData є набором пар key=value, розділених амперсандом (&), аналогічно стандартному URL query string. Серед параметрів передаються query_id, user (JSON-рядок із даними профілю), auth_date (Unix timestamp) і сам hash.
Процес перевірки автентичності складається з п'яти обов'язкових кроків:
- Вилучення підпису: з отриманого рядка відокремлюється параметр
hash. Усі інші параметри залишаються для збирання перевірочного рядка. - Сортування за алфавітом: решта пар
key=valueсортуються за ключем в алфавітному порядку (A-Z). - Формування data_check_string: відсортовані пари об'єднуються через символ перенесення рядка (
). - Генерація HMAC-ключа: створюється байтовий HMAC-SHA-256 геш від рядка
"WebAppData", де ключем виступає токен вашого бота. - Порівняння гешів: обчислюється контрольний HMAC-SHA-256 від
data_check_stringіз використанням згенерованого ключа. Результат порівнюється з отриманимhashчерез функції, стійкі до атак за часом (timing attacks).
Покрокова реалізація парсингу та валідації на чистому PHP
Під час обробки query string не можна використовувати базовий parse_str() без попередньої обробки, оскільки він декодує значення та може змінити структуру JSON усередині параметра user, що порушить підсумковий підпис. Надійніше розібрати рядок вручну або працювати з початковими urldecode-значеннями.
Нижче наведено готовий хелпер для перевірки автентичності initData з обмеженням за часом життя підпису (за замовчуванням — 24 години):
<?php
declare(strict_types=1);
/**
* Валидирует подпись initData от Telegram Mini App.
*
* @param string $initData Сырая строка window.Telegram.WebApp.initData
* @param string $botToken Токен бота из getenv() или конфига
* @param int $maxAge Максимально допустимый возраст подписи в секундах
* @return array|false Возвращает массив распарсенных данных или false при ошибке
*/
function validateTelegramInitData(string $initData, string $botToken, int $maxAge = 86400): array|false
{
if (empty($initData) || empty($botToken)) {
return false;
}
// 1. Разбираем query string с сохранением исходного кодирования
$rawData = explode('&', $initData);
$params = [];
$receivedHash = '';
foreach ($rawData as $pair) {
if (strpos($pair, '=') === false) {
continue;
}
[$key, $value] = explode('=', $pair, 2);
$decodedKey = urldecode($key);
$decodedValue = urldecode($value);
if ($decodedKey === 'hash') {
$receivedHash = $decodedValue;
} else {
$params[$decodedKey] = $decodedValue;
}
}
if (empty($receivedHash)) {
return false;
}
// 2. Сортируем параметры по алфавиту ключей
ksort($params);
// 3. Формируем data_check_string
$dataCheckArr = [];
foreach ($params as $key => $value) {
$dataCheckArr[] = $key . '=' . $value;
}
$dataCheckString = implode("
", $dataCheckArr);
// 4. Генерируем HMAC-SHA-256 секретный ключ
// Константа 'WebAppData' используется в качестве ключа для хэширования токена бота
$secretKey = hash_hmac('sha256', $botToken, 'WebAppData', true);
// 5. Вычисляем итоговый hash
$calculatedHash = hash_hmac('sha256', $dataCheckString, $secretKey);
// 6. Timing-safe сравнение хэшей
if (!hash_equals($calculatedHash, $receivedHash)) {
return false;
}
// 7. Проверка срока жизни auth_date
$authDate = isset($params['auth_date']) ? (int)$params['auth_date'] : 0;
if ($authDate === 0 || (time() - $authDate) > $maxAge) {
return false;
}
// Декодируем поле user, если оно присутствовало
if (isset($params['user'])) {
$params['user'] = json_decode($params['user'], true) ?? $params['user'];
}
return $params;
}
Перевірка auth_date та захист від Replay Attacks
Навіть якщо контрольний геш збігається, вразливість залишається, якщо підпис можна використовувати нескінченно. Зловмисник, який перехопив чужий initData, зможе надсилати його на ваш сервер із будь-якого пристрою.
Перевірка auth_date вирішує цю проблему лише частково, обмежуючи «вікно життя» пакета (наприклад, до 86400 секунд). Щоб виключити повторне використання справжніх даних усередині цього вікна (Replay Attack), необхідно логувати унікальні ідентифікатори на боці бекенду.
- Зберігайте
query_idабо обчисленийhashу Redis / DB із часом життя (TTL), що дорівнює допустимому вікнуauth_date. - Якщо отриманий
hashуже присутній у сховищі, відхиляйте запит із HTTP-кодом403 Forbidden.
Прийом запиту від Mini App та робота з Telegram Bot API
Розглянемо практичний ендпоінт, який приймає initData в HTTP-заголовку, здійснює валідацію, вилучає ID користувача та надсилає йому сервісне сповіщення через метод sendMessage Bot API з використанням cURL.
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
$botToken = getenv('TELEGRAM_BOT_TOKEN') ?: '';
if (empty($botToken)) {
http_response_code(500);
echo json_encode(['ok' => false, 'error' => 'Bot token is not configured']);
exit;
}
// Извлекаем initData из заголовков или POST-тела
$headers = getallheaders();
$initData = $headers['X-Telegram-Init-Data'] ?? $_POST['init_data'] ?? '';
$validData = validateTelegramInitData($initData, $botToken, 86400);
if ($validData === false) {
http_response_code(401);
echo json_encode(['ok' => false, 'error' => 'Invalid or expired initData']);
exit;
}
$user = $validData['user'] ?? null;
if (!is_array($user) || !isset($user['id'])) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'User payload missing']);
exit;
}
$telegramUserId = (int)$user['id'];
// Пример безопасной отправки сообщения пользователю через cURL
$telegramApiUrl = "https://api.telegram.org/bot{$botToken}/sendMessage";
$postFields = [
'chat_id' => $telegramUserId,
'text' => "Успешная авторизация в Mini App! Ваш ID: {$telegramUserId}",
'parse_mode' => 'HTML',
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $telegramApiUrl,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($postFields),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($response === false || $httpCode !== 200) {
// В продакшене необходимо записать $curlError в лог
http_response_code(502);
echo json_encode(['ok' => false, 'error' => 'Failed to notify user via Telegram API']);
exit;
}
$responseData = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE || !($responseData['ok'] ?? false)) {
http_response_code(502);
echo json_encode(['ok' => false, 'error' => 'Telegram API returned error']);
exit;
}
// Успешный ответ клиенту
echo json_encode([
'ok' => true,
'user_id' => $telegramUserId,
'first_name' => $user['first_name'] ?? '',
]);
Часті помилки під час реалізації перевірки
Навіть із правильним алгоритмом розробники припускаються критичних помилок, які призводять або до хибних спрацьовувань, або до вразливостей:
- Забувають аргумент
raw_output = trueу HMAC: Викликhash_hmac('sha256', $botToken, 'WebAppData')без четвертого параметраtrueповертає hex-рядок замість бінарного масиву байтів. Це ламає обчислення фінального гешу. - Порівняння через
===замістьhash_equals(): Оператор суворої рівності перериває порівняння на першому баті, що не збігся, що відкриває можливість атаки за часом (Timing Attack) для підбору підпису. - Мутація JSON-рядка
user: Якщо розкодувати JSON із параметраuserдо обчисленняdata_check_stringі зібрати його назад черезjson_encode(), форматування (пробіли, екранування слешів) зміниться. Підпис перестане збігатися. - Відсутність перевірки HTTP-коду cURL: Під час надсилання запитів до Telegram API необхідно завжди перевіряти HTTP-код відповіді та статус
json_last_error(), щоб вчасно обробляти таймаути та мережеві збої.
Якщо вам потрібна комплексна розробка Telegram Mini App та інфраструктури для обробки заявок, фахівці BotCreator допоможуть спроєктувати архітектуру та захистити API.