Справочник по 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: там данные действительно подписаны, и их можно проверять поле за полем.
Смежные ограничения
Когда подлинность апдейта доказана, следующей проблемой становится его повторное прибытие — смотрите разбор доставки апдейтов не менее одного раза.
О границах того, что Telegram вообще сообщает о судьбе сообщения, читайте заметку об отсутствии подтверждений прочтения.
Единственный отказ доставки, о котором Telegram сообщает явно, описывает разбор блокировки бота пользователем.
Бот, где поддельный апдейт выдал бы реальный доступ, описан в разборе бота с закрытым доступом.