Перейти к содержанию
Innovialabs

Как проверять initData в Telegram Mini App на бэкенде

Продвинутое3 мин

Коротко

Отправляйте на сервер сырую строку initData, пересчитывайте HMAC-SHA-256 с ключом, полученным из токена бота и строки WebAppData, сравнивайте с hash и проверяйте auth_date — данным из initDataUnsafe доверять нельзя никогда.

Короткий ответ

Когда Mini App открывается, Telegram передаёт ему initData — строку в формате query string с данными пользователя и подписью hash. Бэкенд должен:

  1. Получить сырую строку Telegram.WebApp.initData от клиента.
  2. Пересчитать подпись с помощью токена бота и сравнить с hash.
  3. Проверить, что auth_date достаточно свежий.
  4. Только после этого достать user.id и создать сессию.

Почему initDataUnsafe нельзя доверять

Telegram.WebApp.initDataUnsafe — это те же данные, уже разобранные в объект на клиенте. Слово Unsafe в названии — прямое предупреждение:

  • Mini App — обычная веб-страница. Её можно открыть в браузере и подменить window.Telegram или отредактировать запрос в DevTools.
  • Любой может отправить на ваш API {"user": {"id": 123}} и выдать себя за другого пользователя.
  • initDataUnsafe годится только для отображения: имя в шапке, аватар до загрузки данных. Для авторизации, платежей и доступа к данным — только проверенный initData.

Алгоритм проверки подписи

  1. Разберите строку initData как query string. Отложите значение hash.
  2. Остальные пары отсортируйте по ключу и соедините в строку key=value через \n. Значения берите в декодированном виде, но без изменений — JSON в поле user не пересобирайте.
  3. Секретный ключ: HMAC-SHA-256(key="WebAppData", message=bot_token).
  4. Ожидаемая подпись: hex(HMAC-SHA-256(key=secret_key, message=data_check_string)).
  5. Сравните с 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.
  • Разный токен на тестовом и боевом окружении при одном и том же боте.

Частые вопросы

Алгоритм похож, но секретный ключ другой: в Login Widget это SHA-256 от токена, а в Mini App — HMAC-SHA-256 токена с ключом WebAppData. Код из одного случая не подходит для другого без изменений.

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.