Основные симптомы проблемы:
-
Go‑бот стартует,
GET /me(черезapi.Bots.GetBot) отрабатывает, данные о боте приходят. -
В логах видно, что контекст жив, цикл
for upd := range api.GetUpdates(ctx)крутится. - В MAX пишем боту — сообщения помечаются как новые и «непрочитанные».
- Но бот не реагирует: ни одно обновление не попадает в обработчик.
В данном случае при попытке покопать в сторону кода не дало результата, т.е. Go тут не виноват. Да и SDK тоже.
Способы получения сообщений в MAX: webhook и long polling
MAX, как и Telegram, даёт два режима доставки событий:
- Webhook: сам шлёт HTTP‑запросы на ваш URL.
- Long polling (
GET /updates): ваш бот сам периодически ходит за обновлениями.
Ключевой момент: для одного и того же бота это взаимоисключающие режимы. Если бот подписан на webhook — события улетают туда, а GET /updates остаётся пустым.
В нашем случае на проекте был старый no‑code/сервисный бот, который когда‑то получал апдейты через внешний URL. Подписки на webhook никто не снял — в итоге новый Go‑бот честно крутил GetUpdates, но получать там было просто нечего.
Проверяем бота на подписанные события webhook
У MAX есть REST‑метод GET /subscriptions, который возвращает все активные подписки бота на вебхуки.
Пример запроса:
curl -X GET "https://platform-api.max.ru/subscriptions"b -H "Authorization: {access_token}"
Получаем примерно такое:
{
"subscriptions": [
{
"url": "https://fgbnu-fnc-vniimk.cxhub.ru/callback-service/maxCallback/8bc9...",
"update_types": ["message_callback","message_created","bot_started"]
},
{
"url": "https://fgbnu-fnc-vniimk.cxhub.ru/callback-service/maxCallback/52f0...",
"update_types": ["message_callback","message_created","bot_started"]
},
{
"url": "https://b92903.watbot.ru/webhooks/max/2069:W5nod4bY...";
}
]
}
Три боевых webhook‑подписки. Пока они живы, GetUpdates в Go‑боте можно крутить сколько угодно — апдейты там так и не появятся.
Как правильно отписаться от всех webhook’ов
Пара важных деталей:-
Параметр
urlдолжен совпадать точно с тем, что вернулGET /subscriptions(включая путь и идентификаторы). -
Заголовок
Authorization— это простоaccess_tokenбезBearer. -
В bash перенос строки делаем одним обратным слэшем
\в конце, а не\\, иначеcurlразваливается и токен до сервера не доходит.
В нашем случае понадобилось 3 поочерёдных запроса, и финальный запрос на проверку, что не осталось подписок (примеры выше).
{"subscriptions":[]} - значит подписок больше нет и бот работает в режиме long polling.
Почему сообщения были «непрочитанными» в интерфейсе MAX
Это логично, если вспомнить, как работает доставка:
- MAX ставит новое событие в очередь и помечает его как «новое/непрочитанное».
- Если у бота есть webhook — события шлются на указанный URL.
-
Если бот читает через
GET /updates— события снимаются с очереди при успешном ответе.
Пока бот:
- подписан на webhook и вебхук недоступен/обработчик не отвечает;
-
или мы пытаемся читать
GetUpdatesпараллельно с webhook;
Сообщения так и висят непрочитанными. В нашем случае Go‑бот вообще не участвовал в доставке: все события уходили на старые URL, где либо никто не принимал запросы, либо отвечал другой сервис.
После того как мы проделали следующие манипуляции:
-
Удалили все подписки через
DELETE /subscriptions. -
Перезапустили Go‑бота с
GetUpdates. - Написали ему сообщение.
Апдейты пошли, лог ожил, а в интерфейсе MAX сообщения перестали висеть «новыми» — потому что их наконец‑то кто‑то читает.
Что вынесли из этой истории
- Перед тем как дебажить свой код, всегда проверяйте инфраструктуру: токены, подписки, webhook vs long polling.
-
В MAX нельзя одновременно эффективно использовать webhook и
GetUpdatesдля одного бота — нужно выбрать режим и отключить второй. -
Go‑SDK — не «волшебная коробка». Стоит один раз открыть структуры
schemes.*в IDE, и сразу становится понятно, почемуCallback.Payload— это строка, а не объект.
Если вы тоже мигрируете бота с webhook’а на long polling в MAX и видите «тихий» GetUpdates при живом токене — начните с GET /subscriptions. Скорее всего, все ответы уже приходят, просто не к вам.
