Кнопки под сообщением бота не срабатывают: разбор трёх причин

Боты
2026-08-16
#telegram#боты#troubleshooting#api

Пользователь нажимает кнопку под сообщением бота, на кнопке появляется индикатор ожидания и остаётся там навсегда. Задача при этом иногда всё же создаётся, иногда нет. За этим стоят три разных сбоя, и лечатся они по-разному, поэтому первым делом их надо развести.

Три разных сбоя

  1. Нажатие вообще не дошло до вашего приложения.
  2. Приложение его получило, выполнило работу, но не подтвердило нажатие.
  3. Приложение подтвердило слишком поздно, когда идентификатор уже недействителен.

Симптом у всех трёх один — крутящийся индикатор. Дальше разберём, как отличить.

Подтверждение нажатия обязательно

При нажатии inline-кнопки Telegram присылает объект callback_query. Клиент показывает индикатор ожидания до тех пор, пока бот не вызовет answerCallbackQuery. Вызов нужен всегда, даже когда показывать пользователю нечего:

curl -X POST "https://api.telegram.org/bot<ТОКЕН>/answerCallbackQuery" \
  -H "Content-Type: application/json" \
  -d '{"callback_query_id":"<ID_ИЗ_ОБНОВЛЕНИЯ>"}'

Частая ошибка в коде выглядит так: обработчик сразу идёт создавать задачу в Bitrix24, ждёт ответа портала, потом редактирует сообщение и только в конце подтверждает нажатие. Пока портал отвечает, индикатор крутится. Если портал ответил медленно, подтверждение уже опоздало.

Правильный порядок другой:

получить callback_query
→ проверить пользователя и содержимое callback_data
→ сразу answerCallbackQuery
→ выполнить долгую операцию
→ отредактировать сообщение или прислать результат

Подтверждение снимает индикатор и при необходимости показывает всплывающее уведомление. Саму работу оно не выполняет и сообщение не меняет — это отдельные вызовы.

Про таймаут: чего писать не стоит

В интернете гуляют точные числа вроде «десять секунд» и «тридцать секунд». В публичной документации Telegram срок жизни callback_query_id не указан, поэтому опираться на конкретную цифру не стоит. Проверяемый факт другой: на просроченный или неверный идентификатор сервер отвечает

Bad Request: query is too old and response timeout expired
or query ID is invalid

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

Проверка доставки обновлений

Если в логах приложения нажатия не видно вовсе, дело не в подтверждении. Смотрите, как настроена доставка:

curl "https://api.telegram.org/bot<ТОКЕН>/getWebhookInfo"

Что смотреть в ответе:

  • url — установлен ли webhook. Пустая строка означает режим опроса;
  • pending_update_count — копятся ли необработанные обновления;
  • last_error_date и last_error_message — почему Telegram не смог доставить обновление;
  • allowed_updates — есть ли в списке callback_query.

Последний пункт даёт отдельную ловушку. Если при настройке webhook или опроса не передать allowed_updates, Telegram сохраняет предыдущее значение. Однажды заданный список без callback_query продолжает действовать, и нажатия не приходят, пока список не переустановят явно.

Минимальный лог входящего события, по которому потом реально что-то понять:

update_id
callback_query.id
callback_query.from.id
callback_query.data
callback_query.message.chat.id
callback_query.message.message_id

Ошибка 409 Conflict: бот запущен дважды

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

Для режима опроса два параллельных getUpdates конфликтуют, и Telegram прерывает предыдущий запрос:

Conflict: terminated by other getUpdates request;
make sure that only one bot instance is running

Webhook и опрос тоже взаимоисключающие: при установленном webhook опрос вернёт ту же 409.

Что с этим делать:

  • определить текущий режим через getWebhookInfo;
  • для опроса оставить ровно один процесс-получатель;
  • при установленном webhook не запускать getUpdates вообще;
  • не использовать getUpdates как проверку живости работающего бота — такой вызов сам создаст конфликт и заберёт часть обновлений себе.

Несколько реплик приложения за webhook при этом допустимы: конфликт возникает именно между несколькими опрашивающими на одном токене.

Что ещё ломает кнопки

  • callback_data ограничен 64 байтами, и считаются именно байты. Кириллица в UTF-8 занимает два байта на символ, поэтому строка из тридцати русских букв уже за пределом.
  • Содержимое callback_data приходит от клиента, и доверять ему нельзя: проверяйте, тот ли пользователь нажал и разрешено ли ему это действие.
  • Повторное нажатие на ту же кнопку должно обрабатываться идемпотентно, иначе одна задача создаётся дважды.
  • У сообщений, отправленных через inline-режим, обычного message может не быть — вместо него приходит inline_message_id, и редактирование делается по нему.

Порядок диагностики

  1. Видно ли нажатие в логах приложения? Если нет — переходите к getWebhookInfo.
  2. Есть ли callback_query в allowed_updates?
  3. Растёт ли pending_update_count и что в last_error_message?
  4. Не встречается ли в логах 409 Conflict?
  5. Вызывается ли answerCallbackQuery и до долгой операции ли?
  6. Не приходит ли в ответ «query is too old»? Значит, подтверждение опаздывает.

Если чинить некогда

Разбор чужой интеграции по логам занимает у нас обычно час-полтора. Пришлите в Telegram ответ getWebhookInfo с замазанным токеном и кусок лога вокруг нажатия — этого хватает, чтобы назвать причину. Настройка и сопровождение интеграций — услуга интеграций.

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