Добавить в корзинуПозвонить
Найти в Дзене

Как мы оживляли бота для MAX: разбор полётов с webhook, GetUpdates и странным Go‑SDK

Недавно делали боевого бота для MAX‑мессенджера на Go и поймали весьма нетривиальную проблему: бот «запущен», токен валидный, GetUpdates крутится, а в интерфейсе MAX сообщения висят непрочитанными, лог пустой, в чат ничего не прилетает. Знакомо? Разберём, что пошло не так и как это починить. Основные симптомы проблемы: В данном случае при попытке покопать в сторону кода не дало результата, т.е. Go тут не виноват. Да и SDK тоже. MAX, как и Telegram, даёт два режима доставки событий: Ключевой момент: для одного и того же бота это взаимоисключающие режимы. Если бот подписан на webhook — события улетают туда, а GET /updates остаётся пустым. В нашем случае на проекте был старый no‑code/сервисный бот, который когда‑то получал апдейты через внешний URL. Подписки на webhook никто не снял — в итоге новый Go‑бот честно крутил GetUpdates, но получать там было просто нечего. У MAX есть REST‑метод GET /subscriptions, который возвращает все активные подписки бота на вебхуки. Пример запроса: curl -X
Оглавление

Недавно делали боевого бота для MAX‑мессенджера на Go и поймали весьма нетривиальную проблему: бот «запущен», токен валидный, GetUpdates крутится, а в интерфейсе MAX сообщения висят непрочитанными, лог пустой, в чат ничего не прилетает. Знакомо? Разберём, что пошло не так и как это починить.

Основные симптомы проблемы:

  • Go‑бот стартует, GET /me (через api.Bots.GetBot) отрабатывает, данные о боте приходят.
  • В логах видно, что контекст жив, цикл for upd := range api.GetUpdates(ctx) крутится.
  • В MAX пишем боту — сообщения помечаются как новые и «непрочитанные».
  • Но бот не реагирует: ни одно обновление не попадает в обработчик.

В данном случае при попытке покопать в сторону кода не дало результата, т.е. Go тут не виноват. Да и SDK тоже.

Способы получения сообщений в MAX: webhook и long polling

MAX, как и Telegram, даёт два режима доставки событий:

  1. Webhook: сам шлёт HTTP‑запросы на ваш URL.
  2. 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, где либо никто не принимал запросы, либо отвечал другой сервис.

После того как мы проделали следующие манипуляции:

  1. Удалили все подписки через DELETE /subscriptions.
  2. Перезапустили Go‑бота с GetUpdates.
  3. Написали ему сообщение.

Апдейты пошли, лог ожил, а в интерфейсе MAX сообщения перестали висеть «новыми» — потому что их наконец‑то кто‑то читает.

Что вынесли из этой истории

  • Перед тем как дебажить свой код, всегда проверяйте инфраструктуру: токены, подписки, webhook vs long polling.
  • В MAX нельзя одновременно эффективно использовать webhook и GetUpdates для одного бота — нужно выбрать режим и отключить второй.
  • Go‑SDK — не «волшебная коробка». Стоит один раз открыть структуры schemes.* в IDE, и сразу становится понятно, почему Callback.Payload — это строка, а не объект.

Если вы тоже мигрируете бота с webhook’а на long polling в MAX и видите «тихий» GetUpdates при живом токене — начните с GET /subscriptions. Скорее всего, все ответы уже приходят, просто не к вам.

Go Golang MAX Chatbot BotAPI Webhooks LongPolling Backend Интеграции RESTAPI