Как проверять initData в Telegram Mini App на бэкенде
Продвинутое3 минРедакция Innovialabs
Коротко
Отправляйте на сервер сырую строку initData, пересчитывайте HMAC-SHA-256 с ключом, полученным из токена бота и строки WebAppData, сравнивайте с hash и проверяйте auth_date — данным из initDataUnsafe доверять нельзя никогда.
Короткий ответ
Когда Mini App открывается, Telegram передаёт ему initData — строку в формате query string с данными пользователя и подписью hash. Бэкенд должен:
- Получить сырую строку
Telegram.WebApp.initDataот клиента. - Пересчитать подпись с помощью токена бота и сравнить с
hash. - Проверить, что
auth_dateдостаточно свежий. - Только после этого достать
user.idи создать сессию.
Почему initDataUnsafe нельзя доверять
Telegram.WebApp.initDataUnsafe — это те же данные, уже разобранные в объект на клиенте. Слово Unsafe в названии — прямое предупреждение:
- Mini App — обычная веб-страница. Её можно открыть в браузере и подменить
window.Telegramили отредактировать запрос в DevTools. - Любой может отправить на ваш API
{"user": {"id": 123}}и выдать себя за другого пользователя. initDataUnsafeгодится только для отображения: имя в шапке, аватар до загрузки данных. Для авторизации, платежей и доступа к данным — только проверенныйinitData.
Алгоритм проверки подписи
- Разберите строку
initDataкак query string. Отложите значениеhash. - Остальные пары отсортируйте по ключу и соедините в строку
key=valueчерез\n. Значения берите в декодированном виде, но без изменений — JSON в полеuserне пересобирайте. - Секретный ключ:
HMAC-SHA-256(key="WebAppData", message=bot_token). - Ожидаемая подпись:
hex(HMAC-SHA-256(key=secret_key, message=data_check_string)). - Сравните с
hashза постоянное время.
import crypto from "node:crypto";
export function validateInitData(initData, botToken, maxAgeSec = 3600) {
const params = new URLSearchParams(initData);
const hash = params.get("hash");
if (!hash) return null;
params.delete("hash");
const dataCheckString = [...params.entries()]
.sort(([a], [b]) => (a < b ? -1 : 1))
.map(([k, v]) => `${k}=${v}`)
.join("\n");
const secret = crypto.createHmac("sha256", "WebAppData").update(botToken).digest();
const expected = crypto.createHmac("sha256", secret).update(dataCheckString).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(hash, "hex");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return null;
const authDate = Number(params.get("auth_date"));
if (!authDate || Date.now() / 1000 - authDate > maxAgeSec) return null;
return JSON.parse(params.get("user") ?? "null");
}
Готовые реализации есть в популярных библиотеках: например, safe_parse_webapp_init_data в aiogram или пакет @telegram-apps/init-data-node. Если используете их, всё равно проверьте, что включена проверка срока.
Свежесть auth_date
Подпись доказывает подлинность, но не свежесть. Перехваченную строку initData можно отправлять повторно, пока вы её принимаете.
- Задайте максимальный возраст под свой сценарий: для оплаты — короткий, для просмотра каталога — длиннее.
- Используйте
initDataкак одноразовый пропуск: проверили — выдали собственный токен сессии с понятным сроком жизни. - Следите за точным временем на сервере (NTP), иначе свежие данные будут отклоняться.
Как передавать initData на сервер
- Отправляйте строку в заголовке, например
Authorization: tma <initData>— так принято в ряде библиотек экосистемы. - Не кладите
initDataв URL: она попадёт в логи прокси и аналитики. - Проверяйте подпись в одном middleware, а не в каждом обработчике отдельно.
Проверка без токена бота
Иногда данные пользователя нужно проверить стороннему сервису, у которого нет вашего токена. Для этого в initData есть поле signature — подпись Ed25519, которую можно проверить публичным ключом Telegram.
- Строка для проверки начинается с
<bot_id>:WebAppData, дальше через\nидут отсортированные поля безhashиsignature. - Публичные ключи для боевой и тестовой среды опубликованы в документации Telegram.
- Так сервис убеждается, что данные выданы именно для вашего бота, не получая токен.
Токен бота — это полный доступ к боту. Передавать его партнёрам ради проверки подписи нельзя.
Частые ошибки
- JSON-пересборка поля
user: порядок ключей или экранирование меняются — подпись не сходится. - Двойное декодирование или, наоборот, использование закодированных значений.
- Ключ перепутан местами: секрет — это HMAC токена с ключом
WebAppData, а не наоборот. - Проверка подписи без проверки
auth_date. - Разный токен на тестовом и боевом окружении при одном и том же боте.