Зовнішній обробник
Зовнішній обробник — це міст між вашим кодом і 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 без діалогу.