Telegraft

Справочник по Bot API

Как доказать, что апдейт пришёл от Telegram

Метод `setWebhook` принимает необязательный `secret_token` длиной от 1 до 256 символов из набора `A-Z`, `a-z`, `0-9`, `_` и `-`. Telegram затем присылает это значение в заголовке `X-Telegram-Bot-Api-Secret-Token` при каждом обращении к webhook, и именно сверка этого значения отличает настоящий апдейт от того, который вам на адрес отправил кто угодно.

X-Telegram-Bot-Api-Secret-Token: точные цифры

Формат значения secret_token
от 1 до 256 символов из набора A-Z, a-z, 0-9, _ и -
Заголовок
X-Telegram-Bot-Api-Secret-Token
Длина
от 1 до 256 символов
Алфавит
A-Z, a-z, 0-9, подчёркивание, дефис
Ответ при отказе
404 — неавторизованный вызывающий не узнаёт ничего
Ротация
Повторный вызов setWebhook; адрес не меняется

As of 2025-10-01, Telegram Bot API 13.4

Что это значит на практике

Эндпоинт webhook — это публичный HTTPS-адрес, принимающий JSON. Без проверки он принимает этот JSON от любого, кто знает или угадал адрес, а формат полезной нагрузки полностью описан в документации — значит подделать апдейт с любым `chat.id` и любым `from.id` не составляет труда. Для бота, который только повторяет текст, это досадная мелочь; для бота, который выдаёт доступ в канал, начисляет баланс или помечает заказ оплаченным, это отсутствующая модель безопасности целиком.

Ловушка здесь — расчёт на неизвестность адреса, потому что он выглядит как решение. Адрес с токеном бота внутри встречается часто и действительно лучше, чем ничего, но адреса оседают в логах прокси, в панелях CDN, в отчётах об ошибках и в истории браузера у того, кто это тестировал. Секретный токен едет в заголовке, где по умолчанию не логируется и где его можно сменить, не трогая зарегистрированный адрес.

Сама сверка должна быть за постоянное время. Наивное сравнение строк через `===` выдаёт по таймингу длину и совпавший префикс, и хотя эксплуатировать это против webhook сложно, защита стоит одного вызова функции. Важнее на практике правильно отказывать: в ответ на неверный токен надо отдавать 404, а не 401 или 403, потому что первое полезное знание, которое получает атакующий, — это подтверждение того, что маршрут существует.

Ротация устроена просто, и закладывать её стоит сразу. Вызовите `setWebhook` ещё раз с новым `secret_token` — изменение подействует на последующие запросы. Поскольку это заголовок, а не часть адреса, менять в развёртывании больше ничего не нужно, и именно это свойство превращает смену секрета в рутинную операцию, а не в миграцию.

Одну деталь развёртывания легко упустить. Секрет выбираете вы, а не выдаёт Telegram, поэтому он должен быть сгенерирован криптографическим источником случайности и храниться как секрет, а не лежать в репозитории. Запоминающееся значение или значение, выведенное из токена бота, обесценивает всю затею ровно так же, как ставка на неизвестность адреса.

Как это обрабатывать в коде

// Constant-time comparison, and a 404 rather than a 401 on failure.
function constantTimeEquals(a: string, b: string): boolean {
  if (a.length !== b.length) return false
  let diff = 0
  for (let i = 0; i < a.length; i += 1) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)
  return diff === 0
}

function isFromTelegram(request: Request, expected: string): boolean {
  const supplied = request.headers.get('X-Telegram-Bot-Api-Secret-Token') ?? ''
  // An empty expected value must never pass: a misconfigured deploy should fail
  // closed rather than silently accepting every forged update.
  return expected !== '' && constantTimeEquals(supplied, expected)
}
Проверка на пустое ожидаемое значение важна не меньше самой сверки. Развёртывание, в котором секрет забыли задать, обязано отвергать всё, а не принимать всё.

Какие вопросы это порождает

Достаточно ли положить токен бота в адрес webhook?

Это лучше, чем ничего, и хуже, чем заголовок. Адреса утекают в логи прокси, панели CDN, трекеры ошибок и историю браузера так, как заголовки не утекают, а смена адреса означает перерегистрацию webhook вместо замены одного секрета. Делайте и то и другое, если хочется, но полагайтесь на заголовок.

Что реально можно сделать с непроверяемым эндпоинтом бота?

Отправить любой апдейт, описанный в документации, назвав любого пользователя и любой чат. Против бота, который открывает доступ в платный канал, начисляет реферальный баланс или помечает заказ оплаченным, это полный обход авторизации, не требующий никакой квалификации: формат данных опубликован.

Нужно ли дополнительно проверять адрес отправителя?

Как второй слой это бывает полезно, ведь Telegram публикует свои диапазоны адресов, но как основной контроль это слабо: диапазоны меняются, а любая неверная настройка перед вашим сервисом способна переписать видимый источник. Секретный токен — механизм, придуманный именно для этого, и он не зависит от сетевой топологии.

Защищает ли секретный токен содержимое полезной нагрузки?

Нет. Он подтверждает отправителя, а не сообщение. Он доказывает, что запрос пришёл от того, кто владеет общим секретом, но не подписывает тело. Разница существенна при сравнении с init data в Mini App: там данные действительно подписаны, и их можно проверять поле за полем.

Смежные ограничения