Zewnętrzny handler
Zewnętrzny handler to most między Twoim kodem a Telegram. Gdy logika kreatora jest niewystarczająca, oddajesz obsługę konkretnej reakcji własnym serwerem: otrzymuje on zdarzenia i odpowiada komendami w formacie Telegram Bot API, a GetMyBot zajmuje się transportem, dostarczaniem i całą warstwą Telegram.
Czym jest
Reakcje w GetMyBot budowane są wizualnie: wyzwalacz → warunki → akcje. Złożoną lub niestandardową logikę — własną bazę danych, obliczenia, integracje z zewnętrznymi systemami, ML, rozbudowane scenariusze — trudno wyrazić w kreatorze. Zewnętrzny handler usuwa ten sufit: podłączasz własny backend i oddajesz mu całe reakcje do obsługi.
Zewnętrzna usługa otrzymuje od GetMyBot zdarzenia (wyzwolił się wyzwalacz, użytkownik odpowiedział tekstem, nacisnął przycisk) i w odpowiedzi wydaje botowi polecenia, co wysłać. GetMyBot wykonuje komendy własnym tokenem bota — nad tym działają rate-limit, ponowienia, deduplikacja i dziennik dialogu.
Model działania
- GetMyBot jest serwerem WebSocket. Twoja usługa sama łączy się z nim i nie potrzebuje publicznego adresu ani endpointu webhook. GetMyBot nie nawiązuje połączeń wychodzących do Twojej usługi, więc nie ma powierzchni SSRF.
- Uwierzytelnianie tokenem integracji. Usługa przedstawia token przy połączeniu; GetMyBot weryfikuje go względem hasha i utrzymuje połączenie.
- Zdarzenia i komendy w formacie Telegram Bot API. Usługa otrzymuje zdarzenie, a odpowiada tablicą wywołań w formacie „metoda + parametry", dokładnie tak jak przy pracy ze zwykłym botem.
- Komendy wykonuje GetMyBot własnym tokenem. Usługa nigdy nie komunikuje się bezpośrednio z Telegram: komendy przechodzą walidację i trafiają do wewnętrznego outbox GetMyBot, skąd są wysyłane tokenem bota z rate-limit, ponowieniami, deduplikacją i dziennikiem.
Jedno połączenie obsługuje wszystkie dialogi jednej integracji — zdarzenia różnych użytkowników są multipleksowane i rozróżniane po polu session_id.
Konfiguracja integracji
- Otwórz sekcję Integracje w menu po lewej i kliknij Dodaj integrację.
- Wybierz usługę Zewnętrzny handler i podaj nazwę.
- Po utworzeniu karta pokaże:
- Adres WebSocket — w formacie
wss://api.mybot.app/ext/ws/<integrationID>, gdzie<integrationID>to UUID tej integracji. Pod tym adresem łączy się Twoja usługa. Bez ID w ścieżce połączenie nie zostanie nawiązane. - Token połączenia — pokazywany jeden raz przy tworzeniu. Skopiuj go i przechowaj w bezpiecznym miejscu; ponowne wyświetlenie tokenu nie jest możliwe (przechowywany jest wyłącznie jako hash).
- Rotacja tokenu — przycisk generuje nowy token i natychmiast unieważnia stary. Po rotacji zaktualizuj token w swojej usłudze.
- Wskaźnik online — pokazuje, czy Twoja usługa utrzymuje aktywne połączenie w tej chwili.
- Adres WebSocket — w formacie
Dodatkowe parametry integracji wpływające na protokół:
session_ttl_seconds— czas życia sesji w sekundach (domyślnie3600).on_unavailable_reaction_id— reakcja zapasowa uruchamiana, gdy usługa jest offline w momencie wyzwolenia (zob. Niezawodność).
Powiązanie z reakcją
Zewnętrzny handler podłączany jest nie jako osobny wyzwalacz, lecz jako akcja. W edytorze reakcji dodaj akcję Przekaż do zewnętrznego handlera i wybierz odpowiednią integrację.
Dialog może otworzyć dowolny istniejący wyzwalacz GetMyBot — komenda, tekst, kliknięcie przycisku, parametr z linku, przychodzące żądanie web, harmonogram. Gdy taki wyzwalacz zostaje uruchomiony i dotrze do akcji, GetMyBot otwiera sesję proxy i wysyła usłudze zdarzenie session.open.
Jeśli w tym momencie usługa nie jest podłączona, GetMyBot uruchomi reakcję zapasową z ustawień integracji (on_unavailable_reaction_id). Jeśli reakcja zapasowa nie jest skonfigurowana — nic się nie wydarzy (bez błędu dla użytkownika).
Połączenie i autoryzacja
Endpoint: GET /ext/ws/{integrationID} przez WebSocket (wss). integrationID — UUID integracji typu „Zewnętrzny handler". Limit rozmiaru ramki — 256 KiB.
Autoryzacja możliwa jest na dwa sposoby.
1. Nagłówek przy uzgadnianiu (preferowane). Przekaż token w nagłówku Authorization:
Authorization: Bearer <token>
2. Ramka auth. Jeśli nagłówek Bearer nie został przesłany, w ciągu 10 sekund od nawiązania połączenia wyślij jako pierwszą ramkę:
{ "type": "auth", "token": "<token>" }
Jeśli ramka auth nie nadejdzie w ciągu 10 sekund — połączenie zostanie zamknięte kodem 4401. Nieprawidłowy token przy obu metodach — również close 4401.
Token generowany jest przy tworzeniu integracji, pokazywany jeden raz i przechowywany wyłącznie jako hash (sha256), weryfikacja w czasie stałym (constant-time). Rotacja tokenu:
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
Endpoint zwraca nowy token w postaci plaintext; stary natychmiast przestaje działać.
Zdarzenia: bot → usługa
Każde zdarzenie to ramka JSON (EventEnvelope). Pole type — jedno z: session.open, message, callback, session.cancel, session.expired.
Pełny zestaw pól koperty:
{
"type": "session.open",
"session_id": "string",
"bot_id": "string",
"user": {
"tg_user_id": 123456789,
"first_name": "Jan",
"username": "jan",
"params": { "utm": "promo" }
},
"chat": { "id": 123456789, "type": "private" },
"update": { "...": "surowy Telegram Update (raw JSON)" },
"event": { "...": "znormalizowane zdarzenie GetMyBot (raw JSON)" }
}
session_id— identyfikator sesji (dialogu).bot_id— identyfikator bota.user— dane użytkownika:tg_user_id(int64), opcjonalnefirst_name,username, a takżeparams— parametry użytkownika (w tym z linku), mapa klucz→wartość.chat— czat:id(int64) itype(np."private").update— surowy Telegram Update (raw JSON). Obecny dlasession.open,message,callback.event— znormalizowane zdarzenie GetMyBot (raw JSON).
Zawartość różni się w zależności od typu:
session.open— wyzwalacz zadziałał, otwarty nowy dialog. Zawieraupdate(surowy Update) ievent(pełne znormalizowane zdarzenie),user.paramssą wypełnione.message— wysyłane, gdy usługa „utrzymuje oczekiwanie" (expect=text/any) i użytkownik przesłał wiadomość. Zawartość taka sama jak wsession.open.callback— użytkownik nacisnął przycisk wcześniej wysłany przez usługę.updatesurowy; wuserwypełniony tylkotg_user_id;eventzawiera wyłącznie oryginalnecallback_datapodane przez usługę w przycisku:
{ "callback_data": "<oryginalna wartość podana przez usługę w przycisku>" }
session.cancel/session.expired— minimalna koperta:session_id,bot_id,user.tg_user_id,chat.id(dlacancelrównieżchat.type). Polaupdate/eventnie są obecne. Zdarzenia te dostarczane są tylko jeśli usługa jest online — nie są zapisywane do kolejki offline.
Komendy: usługa → bot
Komenda to ramka JSON (CommandEnvelope). Pole type — jedno z: execute, session.close, auth, ping, pong.
{
"type": "execute",
"session_id": "string",
"methods": [
{ "method": "sendMessage", "params": { "text": "Cześć" } }
],
"expect": "none"
}
session_id— sesja, do której odnosi się komenda.token— tylko dlatype: "auth"(zob. Połączenie i autoryzacja).methods— tablica obiektów{ "method": "<nazwa metody Telegram Bot API>", "params": { ... } }. Nazwa metody — dokładnie tak jak w Telegram Bot API (np.sendMessage),params— obiekt parametrów tej metody.expect— czy usługa oczekuje odpowiedzi użytkownika:"none","text"lub"any"(pusta wartość traktowana jest jakonone).session.close— jawnie zamknij dialog.
Komendy nie trafiają bezpośrednio do Telegram: GetMyBot waliduje je i umieszcza w swoim outbox, skąd wysyła tokenem bota (z rate-limit, ponowieniami, deduplikacją, dziennikiem).
Jak przetwarzane jest execute
Komenda execute jest odrzucana w całości, jeśli: session_id jest pusty; sesja nie została znaleziona lub nie ma statusu open; sesja należy do innej integracji; sesja wygasła.
Następnie:
- Rate-limit: do 60
executena minutę na sesję (okno kroczące). Przekroczenie limitu — odrzucenie. - Do 30 metod w jednym
execute; nadmiarowe są po cichu odrzucane. - Każda metoda jest sprawdzana względem whitelist (zob. Dozwolone metody) — metoda spoza listy jest odrzucana.
- Chat-guard: jeśli w
paramsjestchat_idlubfrom_chat_idz wartością różną od0i odchat_idsesji — cała metoda jest odrzucana (nie można wysyłać do cudzego czatu).chat_idmożna w ogóle pominąć: GetMyBot wymuszy czat sesji. - Przyciski inline z
callback_datasą automatycznie tokenizowane (wewnętrzny formatx:<token>); czas życia przycisku = do wygaśnięcia sesji. Przyciskiurl/webapp/switch_inlinenie są modyfikowane.
Dozwolone metody
Usługa steruje botem, więc zestaw metod jest ograniczony whitelistą — tylko wysyłka i praca z wiadomościami bieżącego dialogu. Metody spoza listy są po cichu ignorowane (to nie jest błąd).
- wysyłka:
sendMessage,sendPhoto,sendDocument,sendVideo,sendAudio,sendMediaGroup,sendAnimation,sendVoice,sendLocation,sendChatAction; - edytowanie i usuwanie:
editMessageText,editMessageCaption,editMessageReplyMarkup,deleteMessage; - odpowiedź na kliknięcie:
answerCallbackQuery; - przekazywanie i kopiowanie:
forwardMessage,copyMessage; - przypinanie:
pinChatMessage,unpinChatMessage.
Metody na poziomie konta (setWebhook, getUpdates, logOut, close, setMyCommands itd.) nie są dozwolone.
Oczekiwanie i cykl życia sesji
Po każdym execute los sesji zależy od expect i od tego, czy w komendzie były przyciski inline z callback_data:
expect=textlubany— kolejna wiadomość użytkownika zostanie przekazana usłudze zdarzeniemmessage.expect=noneI w komendzie NIE było przycisków inline z callback — sesja jest automatycznie zamykana. To „terminalna" wiadomość.expect=none, ALE są przyciski callback — sesja pozostaje otwarta: kliknięcia są przechwytywane, dopóki żyją tokeny przycisków (do TTL).
Ważne: expect nie wpływa na odbiór kliknięć przycisków. Kliknięcia tokenizowanych przycisków są przechwytywane niezależnie od wartości expect — dopóki sesja jest aktywna i token przycisku żyje. expect kontroluje wyłącznie to, czy usługa oczekuje tekstowej/dowolnej odpowiedzi.
Granice dialogu
Kontekst dialogu żyje dokładnie tak długo, jak sesja jest otwarta:
- Odpowiedź tekstowa przy aktywnym oczekiwaniu (
expect=text/any) — trafia do usługi zdarzeniemmessage, a nie uruchamia zwykłych reakcji bota. - Kliknięcie tokenizowanego przycisku — trafia do usługi zdarzeniem
callbackz oryginalnymcallback_data. GetMyBot zawsze sam potwierdza kliknięcie (answerCallbackQuery); sprawdzana jest przynależność kliknięcia (bot + użytkownik + czat sesji). Kliknięcie przycisku nie zamyka sesji. /cancel(a także/cancel@boti „anuluj") przy aktywnej sesji zewnętrznej — sesja jest zamykana, usłudze wysyłane jestsession.cancel, a użytkownikowi „Anulowano.".- Jeden dialog na użytkownika. Otwarcie nowej sesji wypiera poprzednią otwartą sesję tego użytkownika — otrzymuje ona
session.cancel. - Wygaśnięcie TTL. Po upływie timera sesja oznaczana jest jako
expiredi (jeśli usługa jest online) do usługi wysyłane jestsession.expired.
Limity i timeouty
- Rozmiar ramki: 256 KiB.
- TTL sesji: domyślnie 3600 sekund (konfigurowane parametrem
session_ttl_secondsintegracji). - Częstotliwość komend: 60
executena minutę na sesję (okno kroczące). - Metod w jednym
execute: do 30 (nadmiarowe są odrzucane). - Otwartych sesji na integrację: do 1000.
- Kolejka offline: do 100 zdarzeń na sesję.
- Heartbeat:
pingco 30 sekund; timeout braku aktywności — 60 sekund. - Timeout ramki auth: 10 sekund.
Niezawodność (heartbeat, offline, kolejka, fallback)
- Heartbeat. Serwer co 30 sekund wysyła ramkę
{"type":"ping"}. Jeśli od klienta nie ma aktywności przez ponad 60 sekund — połączenie jest zamykane. Klient może wysyłać własneping/pongw celu utrzymania aktywności. Zerwanie połączenia nie niszczy dialogów: sesje żyją w bazie danych do TTL lub ponownego połączenia. - Ponowne połączenie — po stronie usługi. Jeśli połączenie zostało zerwane, Twoja usługa musi połączyć się ponownie. Otwarte sesje w bazie przeżywają restarty do wygaśnięcia TTL.
- Fallback przy offline na starcie. Jeśli w momencie wyzwolenia reakcji usługa nie jest podłączona — uruchamiana jest reakcja zapasowa z ustawień integracji (
on_unavailable_reaction_id). Jeśli nie jest skonfigurowana — nic się nie dzieje (bez błędu). - Kolejka offline. Zdarzenia
message/callbackniedostarczone z powodu offline są umieszczane w kolejce offline (do 100 zdarzeń, FIFO, przechowywana do wygaśnięcia sesji) i dosyłane przy ponownym połączeniu. Zdarzeniasession.open/session.cancel/session.expirednie są zapisywane do kolejki. - Gwarancja dostarczenia — at-most-once. Przy przepełnieniu lub wygaśnięciu kolejki zdarzenia są odrzucane. Projektuj logikę tak, by pominięcie pojedynczego zdarzenia nie przerywało scenariusza.
Wskaźnik online
Możesz sprawdzić, czy usługa utrzymuje aktywne połączenie, przez endpoint:
GET /api/bots/{botID}/integrations/{integrationID}/status
Odpowiedź:
{ "online": true }
Ten sam wskaźnik dostępny jest na karcie integracji w panelu. Odpowiednik w MCP — narzędzie get_integration_status.
Bezpieczeństwo
GetMyBot wykonuje komendy zewnętrznej usługi własnym tokenem, dlatego ochrona jest rygorystyczna.
- Brak powierzchni SSRF. GetMyBot jest serwerem WebSocket i sam nie nawiązuje połączeń wychodzących do usługi — to usługa łączy się z GetMyBot. Nie istnieje URL kontrolowany z zewnątrz, pod który platforma by się odwoływała.
- Token przechowywany jako hash (sha256), weryfikacja w czasie stałym, obsługiwana rotacja.
- Izolacja dzierżawcy. Komenda jest ściśle powiązana z własną integracją; sesja — z botem + użytkownikiem + czatem. Zdarzenia cudzych botów nie trafiają do Twojego gniazda.
- Whitelist metod + wymuszony
chat_iduniemożliwiają przekształcenie bota w rozsyłacza do dowolnych czatów. - Token bota nie jest przekazywany na zewnątrz — usługa nigdy nie komunikuje się bezpośrednio z Telegram.
- Maskowanie sekretów. Token integracji i wrażliwe wartości są ukrywane w dzienniku i logach.
Przykłady
Minimalny handler: na otwarcie dialogu wysyła wiadomość i zamyka sesję (expect: "none"). Podstaw ID integracji i token. Adres musi koniecznie zawierać <integrationID> w ścieżce.
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: "Cześć od zewnętrznego handlera!" } }],
}));
}
});
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": "Cześć od zewnętrznego handlera!"}}
],
}))
asyncio.run(main())
Przycisk inline i obsługa callback
Aby przechwycić kliknięcie, wyślij przycisk z callback_data (GetMyBot tokenizuje go automatycznie). Przy expect: "none" z przyciskiem sesja pozostaje otwarta, dopóki żyje token przycisku — kliknięcie przyjdzie zdarzeniem callback, w którym event.callback_data jest równe oryginalnej wartości. Potwierdzanie kliknięcia (answerCallbackQuery) nie jest potrzebne — GetMyBot robi to sam.
if (ev.type === "session.open") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{
method: "sendMessage",
params: {
text: "Naciśnij przycisk",
reply_markup: { inline_keyboard: [[{ text: "Jedziemy", 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: "Przycisk naciśnięty!" } }],
}));
}
Co dalej
- Integracje — ogólny przegląd podłączanych usług.
- Reakcje — jak budowane są wyzwalacze, warunki i akcje.
- Żądania web i webhooki — prostszy sposób wywołania zewnętrznego URL bez dialogu.