Внешний обработчик
Подключение своего бэкенда к боту по WebSocket: события и команды в формате Telegram Bot API.
На этой странице
Внешний обработчик: это мост между вашим кодом и Telegram. Когда логики из конструктора не хватает, вы отдаёте отдельную реакцию своему серверу: он получает события и отвечает командами в формате Telegram Bot API, а GetMyBot берёт на себя транспорт, доставку и весь Telegram.
Что это
Реакции в GetMyBot собираются визуально: триггер → условия → действия. Сложную или нестандартную логику: свою базу, расчёты, обращения к чужим системам, ML, ветвистые сценарии: в конструкторе выразить тяжело. Внешний обработчик снимает этот потолок: вы подключаете собственный бэкенд и отдаёте ему на обработку целые реакции.
Внешний сервис получает от GetMyBot события (сработал триггер, пользователь ответил текстом, нажал кнопку) и в ответ командует боту, что отправить. GetMyBot исполняет команды своим токеном бота: поверх работают rate-limit, ретраи, дедупликация, журнал диалога.
Модель работы
- GetMyBot: это WebSocket-сервер. Ваш сервис сам подключается к нему и не нуждается в публичном адресе или вебхук-эндпоинте. Исходящих соединений к вашему сервису GetMyBot не делает, поэтому SSRF-поверхности нет.
- Аутентификация: токеном интеграции. Сервис предъявляет токен при подключении; GetMyBot сверяет его с хешем и держит соединение.
- События и команды: в формате Telegram Bot API. Сервис получает событие, а отвечает массивом вызовов вида «метод + параметры», точь-в-точь как при работе с обычным ботом.
- Команды исполняет GetMyBot своим токеном. Сервис никогда не обращается к Telegram напрямую: команды проходят валидацию и попадают во внутренний outbox GetMyBot, откуда отправляются токеном бота с rate-limit, ретраями, дедупликацией и журналом.
Одно соединение обслуживает все диалоги одной интеграции: события разных пользователей мультиплексируются и различаются по полю session_id.
Настройка интеграции
- Откройте раздел Интеграции в меню слева и нажмите Добавить интеграцию.
- Выберите сервис Внешний обработчик и задайте название.
- После создания карточка покажет:
- Адрес WebSocket: вида
wss://api.mybot.app/ext/ws/<integrationID>, где<integrationID>: UUID этой интеграции. По нему подключается ваш сервис. Без id в пути соединение не установится. - Токен подключения: показывается один раз при создании. Скопируйте и сохраните его в надёжном месте; повторно посмотреть токен нельзя (он хранится только хешем).
- Перевыпуск токена: кнопка генерирует новый токен и сразу обесценивает старый. После перевыпуска обновите токен в своём сервисе.
- Индикатор онлайн: показывает, держит ли ваш сервис активное соединение прямо сейчас.
- Адрес WebSocket: вида
Дополнительные параметры интеграции, влияющие на протокол:
session_ttl_seconds: время жизни сессии в секундах (по умолчанию3600).on_unavailable_reaction_id: запасная реакция, которая запускается, если сервис офлайн в момент срабатывания (см. Надёжность).
Привязка к реакции
Внешний обработчик подключается не отдельным триггером, а действием. В редакторе реакции добавьте действие Передать внешнему обработчику и выберите нужную интеграцию.
Открыть диалог может любой существующий триггер GetMyBot: команда, текст, нажатие кнопки, параметр из ссылки, входящий веб-запрос, расписание. Когда такой триггер срабатывает и доходит до действия, GetMyBot открывает прокси-сессию и шлёт сервису событие session.open.
Если в этот момент сервис не подключён, GetMyBot запустит запасную реакцию из настройки интеграции (on_unavailable_reaction_id). Если запасная не задана: ничего не произойдёт (тихо, без ошибки пользователю).
Подключение и авторизация
Эндпоинт: GET /ext/ws/{integrationID} поверх WebSocket (wss). integrationID: UUID интеграции типа «Внешний обработчик». Лимит размера кадра: 256 KiB.
Авторизоваться можно одним из двух способов.
1. Заголовок при рукопожатии (предпочтительно). Передайте токен в заголовке Authorization:
Authorization: Bearer <token>
2. Auth-кадром. Если Bearer-заголовок не передан, первым кадром в течение 10 секунд после установления соединения отправьте:
{ "type": "auth", "token": "<token>" }
Если за 10 секунд auth-кадр не пришёл: соединение закрывается с кодом 4401. Неверный токен в любом из способов: тоже close 4401.
Токен генерируется при создании интеграции, показывается один раз и хранится только хешем (sha256), сверка: constant-time. Ротация токена:
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
Эндпоинт возвращает новый plaintext-токен; старый сразу перестаёт работать.
События: бот → сервис
Каждое событие: JSON-кадр (EventEnvelope). Поле type: одно из: session.open, message, callback, session.cancel, session.expired.
Полный набор полей конверта:
{
"type": "session.open",
"session_id": "string",
"bot_id": "string",
"user": {
"tg_user_id": 123456789,
"first_name": "Иван",
"username": "ivan",
"params": { "utm": "promo" }
},
"chat": { "id": 123456789, "type": "private" },
"update": { "...": "сырой Telegram Update (raw JSON)" },
"event": { "...": "нормализованное событие GetMyBot (raw JSON)" }
}
session_id: идентификатор сессии (диалога).bot_id: идентификатор бота.user: данные пользователя:tg_user_id(int64), опциональныеfirst_name,username, а такжеparams: параметры пользователя (в т.ч. из ссылки), map ключ→значение.chat: чат:id(int64) иtype(например"private").update: сырой Telegram Update (raw JSON). Присутствует дляsession.open,message,callback.event: нормализованное событие GetMyBot (raw JSON).
Наполнение по типам различается:
session.open: сработал триггер, открыт новый диалог. Естьupdate(сырой Update) иevent(полное нормализованное событие),user.paramsзаполнены.message: приходит, когда сервис «держит ожидание» (expect=text/any) и пользователь прислал сообщение. Наполнение такое же, как уsession.open.callback: пользователь нажал кнопку, которую раньше прислал сервис.updateсырой; вuserзаполнен толькоtg_user_id;eventсодержит только исходный callback_data, заданный сервисом в кнопке:
{ "callback_data": "<оригинальное значение, заданное сервисом в кнопке>" }
session.cancel/session.expired: минимальный конверт:session_id,bot_id,user.tg_user_id,chat.id(дляcancelещёchat.type). Полейupdate/eventнет. Эти события доставляются только если сервис онлайн: в офлайн-очередь они не кладутся.
Команды: сервис → бот
Команда: JSON-кадр (CommandEnvelope). Поле type: одно из: execute, session.close, auth, ping, pong.
{
"type": "execute",
"session_id": "string",
"methods": [
{ "method": "sendMessage", "params": { "text": "Привет" } }
],
"expect": "none"
}
session_id: сессия, к которой относится команда.token: только дляtype: "auth"(см. Подключение и авторизация).methods: массив объектов{ "method": "<имя метода Telegram Bot API>", "params": { ... } }. Имя метода: точь-в-точь как в Telegram Bot API (напримерsendMessage),params: объект параметров этого метода.expect: ждёт ли сервис ответа пользователя:"none","text"или"any"(пустое значение трактуется какnone).session.close: явно закрыть диалог.
Команды не уходят в Telegram напрямую: GetMyBot валидирует их и кладёт в свой outbox, откуда отправляет токеном бота (с rate-limit, ретраями, дедупликацией, журналом).
Как обрабатывается execute
Команда execute дропается целиком, если: session_id пуст; сессия не найдена или не в статусе open; сессия принадлежит другой интеграции; сессия просрочена.
Далее:
- Rate-limit: до 60
executeв минуту на сессию (скользящее окно). Сверх лимита: дроп. - До 30 методов в одном
execute; лишние молча отбрасываются. - На каждый метод проверяется whitelist (см. Разрешённые методы): метод не из списка отклоняется.
- Chat-guard: если в
paramsестьchat_idилиfrom_chat_idсо значением, не равным0и не равнымchat_idсессии, весь метод отклоняется (нельзя слать в чужой чат).chat_idможно не указывать вовсе: GetMyBot принудительно проставит чат сессии. - Inline-кнопки с
callback_dataавтоматически токенизируются (внутренний видx:<token>); время жизни кнопки = до истечения сессии. Кнопкиurl/webapp/switch_inlineне трогаются.
Разрешённые методы
Сервис командует ботом, поэтому набор методов ограничен whitelist: только отправка и работа с сообщениями текущего диалога. Методы не из списка молча игнорируются (это не ошибка).
- отправка:
sendMessage,sendPhoto,sendDocument,sendVideo,sendAudio,sendMediaGroup,sendAnimation,sendVoice,sendLocation,sendChatAction; - правка и удаление:
editMessageText,editMessageCaption,editMessageReplyMarkup,deleteMessage; - ответ на нажатие:
answerCallbackQuery; - пересылка и копирование:
forwardMessage,copyMessage; - закрепление:
pinChatMessage,unpinChatMessage.
Аккаунт-уровневые методы (setWebhook, getUpdates, logOut, close, setMyCommands и т.п.) не разрешены.
Ожидание и жизненный цикл сессии
После каждого execute судьба сессии зависит от expect и от того, были ли в команде inline-кнопки с callback_data:
expect=textилиany: следующее сообщение пользователя будет переслано сервису событиемmessage.expect=noneИ в команде НЕ было inline-кнопок с callback: сессия автоматически закрывается. Это «терминальное» сообщение.expect=none, НО есть callback-кнопки: сессия остаётся открытой: нажатия ловятся, пока живы токены кнопок (до TTL).
Важно: expect не влияет на приём нажатий кнопок. Нажатия токенизированных кнопок ловятся независимо от значения expect: пока жива сессия и токен кнопки. expect управляет только тем, ждёт ли сервис текстового/произвольного ответа.
Границы диалога
Контекст диалога живёт ровно до тех пор, пока сессия открыта:
- Текстовый ответ при активном ожидании (
expect=text/any): уходит сервису событиемmessage, а не запускает обычные реакции бота. - Нажатие токенизированной кнопки: уходит сервису событием
callbackс оригинальнымcallback_data. GetMyBot всегда сам подтверждает нажатие (answerCallbackQuery); проверяется принадлежность нажатия (бот + пользователь + чат сессии). Кнопкой сессия не закрывается. /cancel(а также/cancel@botи «отмена») при активной внешней сессии: сессия закрывается, сервису шлётсяsession.cancel, пользователю отправляется «Отменено.».- Один диалог на пользователя. Открытие новой сессии вытесняет прежнюю открытую сессию этого пользователя: ей шлётся
session.cancel. - Истечение TTL. По таймеру сессия помечается
expiredи (если сервис онлайн) сервису шлётсяsession.expired.
Лимиты и таймауты
- Размер кадра: 256 KiB.
- TTL сессии: по умолчанию 3600 секунд (настраивается параметром
session_ttl_secondsинтеграции). - Частота команд: 60
executeв минуту на сессию (скользящее окно). - Методов в одном
execute: до 30 (лишние отбрасываются). - Открытых сессий на интеграцию: до 1000.
- Офлайн-очередь: до 100 событий на сессию.
- Heartbeat:
pingкаждые 30 секунд; таймаут отсутствия активности: 60 секунд. - Таймаут auth-кадра: 10 секунд.
Надёжность (heartbeat, офлайн, очередь, fallback)
- Heartbeat. Сервер раз в 30 секунд шлёт кадр
{"type":"ping"}. Если от клиента нет активности дольше 60 секунд: соединение закрывается. Клиент может слать своиping/pongдля поддержания активности. Разрыв соединения не убивает диалоги: сессии живут в БД до TTL или переподключения. - Переподключение: на стороне сервиса. Если соединение оборвалось, ваш сервис должен подключиться заново. Открытые сессии в БД переживают рестарты до истечения TTL.
- Fallback при офлайне на старте. Если в момент срабатывания реакции сервис не подключён: запускается запасная реакция из настройки интеграции (
on_unavailable_reaction_id). Если она не задана: ничего не происходит (тихо). - Офлайн-очередь. События
message/callback, не доставленные из-за офлайна, складываются в офлайн-очередь (до 100 событий, FIFO, хранится до истечения сессии) и досылаются при переподключении. Событияsession.open/session.cancel/session.expiredв очередь не кладутся. - Гарантия доставки: at-most-once. При переполнении или протухании очереди события отбрасываются. Проектируйте логику так, чтобы пропуск отдельного события не ломал сценарий.
Индикатор онлайн
Проверить, держит ли сервис активное соединение, можно эндпоинтом:
GET /api/bots/{botID}/integrations/{integrationID}/status
Ответ:
{ "online": true }
Тот же индикатор доступен на карточке интеграции в кабинете. Аналог в MCP: инструмент get_integration_status.
Безопасность
GetMyBot исполняет команды стороннего сервиса своим токеном, поэтому защита строгая.
- Нет SSRF-поверхности. GetMyBot выступает WebSocket-сервером и сам исходящих соединений к сервису не делает: это сервис подключается к GetMyBot. Управляемого извне URL, по которому платформа куда-то ходит, нет.
- Токен хранится хешем (sha256), сверка constant-time, поддержана ротация.
- Изоляция арендатора. Команда жёстко привязана к своей интеграции; сессия: к боту + пользователю + чату. События чужих ботов в ваш сокет не попадают.
- Whitelist методов + принудительный
chat_idне дают превратить бота в рассыльщик в произвольные чаты. - Токен бота наружу не передаётся: сервис никогда не общается с Telegram напрямую.
- Маскирование секретов. Токен интеграции и чувствительные значения скрываются в журнале и логах.
Примеры
Минимальный обработчик: на открытие диалога отправляет сообщение и закрывает сессию (expect: "none"). Подставьте id интеграции и токен. Адрес обязательно содержит <integrationID> в пути.
Node.js
import WebSocket from "ws";
const URL = `wss://api.mybot.app/ext/ws/${process.env.MYBOT_INTEGRATION_ID}`;
const ws = new WebSocket(URL, {
headers: { Authorization: `Bearer ${process.env.MYBOT_TOKEN}` },
});
ws.on("message", (raw) => {
const ev = JSON.parse(raw);
if (ev.type === "session.open") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{ method: "sendMessage", params: { text: "Привет от внешнего обработчика!" } }],
}));
}
});
Python
import json, os, asyncio, websockets
async def main():
url = f"wss://api.mybot.app/ext/ws/{os.environ['MYBOT_INTEGRATION_ID']}"
headers = {"Authorization": f"Bearer {os.environ['MYBOT_TOKEN']}"}
async with websockets.connect(url, additional_headers=headers) as ws:
async for raw in ws:
ev = json.loads(raw)
if ev["type"] == "session.open":
await ws.send(json.dumps({
"type": "execute",
"session_id": ev["session_id"],
"expect": "none",
"methods": [
{"method": "sendMessage", "params": {"text": "Привет от внешнего обработчика!"}}
],
}))
asyncio.run(main())
Inline-кнопка и обработка callback
Чтобы поймать нажатие, пришлите кнопку с callback_data (GetMyBot токенизирует её автоматически). При expect: "none" с кнопкой сессия остаётся открытой, пока жив токен кнопки: нажатие придёт событием callback, в котором event.callback_data равен исходному значению. Подтверждать нажатие (answerCallbackQuery) не нужно: GetMyBot делает это сам.
if (ev.type === "session.open") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{
method: "sendMessage",
params: {
text: "Нажмите кнопку",
reply_markup: { inline_keyboard: [[{ text: "Поехали", callback_data: "go" }]] },
},
}],
}));
} else if (ev.type === "callback" && ev.event.callback_data === "go") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{ method: "sendMessage", params: { text: "Кнопка нажата!" } }],
}));
}
Что дальше
- Интеграции: общий обзор подключаемых сервисов.
- Реакции: как собираются триггеры, условия и действия.
- Веб-запросы и вебхуки: более простой способ дёрнуть внешний URL без диалога.