Gestore esterno

Il gestore esterno è un ponte tra il tuo codice e Telegram. Quando la logica del costruttore non è sufficiente, delega una singola reazione al tuo server: quest'ultimo riceve eventi e risponde con comandi nel formato Telegram Bot API, mentre GetMyBot si occupa del trasporto, della consegna e di tutto ciò che riguarda Telegram.

Cos'è

Le reazioni in GetMyBot vengono costruite visivamente: trigger → condizioni → azioni. Logiche complesse o non standard — database propri, calcoli, chiamate a sistemi esterni, ML, scenari ramificati — sono difficili da esprimere nel costruttore. Il gestore esterno rimuove questo limite: colleghi il tuo backend e gli affidi l'elaborazione di intere reazioni.

Il servizio esterno riceve da GetMyBot gli eventi (trigger attivato, utente ha risposto con un testo, ha premuto un pulsante) e in risposta comanda al bot cosa inviare. GetMyBot esegue i comandi con il proprio token bot — in cima funzionano rate-limit, tentativi, deduplicazione e registro del dialogo.

Modello di funzionamento

  • GetMyBot è il server WebSocket. Il tuo servizio si connette a esso autonomamente e non necessita di un indirizzo pubblico o di un endpoint webhook. GetMyBot non effettua connessioni in uscita verso il tuo servizio, quindi non esiste una superficie SSRF.
  • Autenticazione tramite token di integrazione. Il servizio presenta il token alla connessione; GetMyBot lo confronta con l'hash e mantiene la connessione.
  • Eventi e comandi nel formato Telegram Bot API. Il servizio riceve un evento e risponde con un array di chiamate del tipo «metodo + parametri», esattamente come quando si lavora con un bot normale.
  • I comandi vengono eseguiti da GetMyBot con il proprio token. Il servizio non comunica mai direttamente con Telegram: i comandi vengono validati e inseriti nell'outbox interno di GetMyBot, da dove vengono inviati con il token del bot con rate-limit, tentativi, deduplicazione e registro.

Un'unica connessione gestisce tutti i dialoghi di un'integrazione — gli eventi di utenti diversi vengono multiplexati e distinti tramite il campo session_id.

Configurazione dell'integrazione

  1. Apri la sezione Integrazioni nel menu a sinistra e clicca su Aggiungi integrazione.
  2. Seleziona il servizio Gestore esterno e inserisci un nome.
  3. Dopo la creazione, la scheda mostrerà:
    • Indirizzo WebSocket — del tipo wss://api.mybot.app/ext/ws/<integrationID>, dove <integrationID> è l'UUID di questa integrazione. Il tuo servizio si connette tramite questo indirizzo. Senza l'id nel percorso la connessione non viene stabilita.
    • Token di connessione — viene mostrato una sola volta alla creazione. Copialo e conservalo in un luogo sicuro; non è possibile visualizzare il token in seguito (viene conservato solo come hash).
    • Rigenera token — il pulsante genera un nuovo token e invalida immediatamente quello precedente. Dopo la rigenerazione aggiorna il token nel tuo servizio.
    • Indicatore online — mostra se il tuo servizio mantiene una connessione attiva in questo momento.

Parametri aggiuntivi dell'integrazione che influenzano il protocollo:

  • session_ttl_seconds — durata della sessione in secondi (valore predefinito: 3600).
  • on_unavailable_reaction_id — reazione di riserva che viene avviata se il servizio è offline al momento dell'attivazione (vedi Affidabilità).

Collegamento a una reazione

Il gestore esterno non viene collegato come trigger separato, ma come azione. Nell'editor della reazione aggiungi l'azione Trasferisci al gestore esterno e seleziona l'integrazione desiderata.

Il dialogo può essere aperto da qualsiasi trigger GetMyBot esistente — comando, testo, pressione di un pulsante, parametro da un link, richiesta web in entrata, pianificazione. Quando tale trigger si attiva e raggiunge l'azione, GetMyBot apre una sessione proxy e invia al servizio l'evento session.open.

Se in quel momento il servizio non è connesso, GetMyBot avvierà la reazione di riserva dalle impostazioni dell'integrazione (on_unavailable_reaction_id). Se la reazione di riserva non è configurata — non accade nulla (silenziosamente, senza errore per l'utente).

Connessione e autorizzazione

Endpoint: GET /ext/ws/{integrationID} tramite WebSocket (wss). integrationID — UUID dell'integrazione di tipo «Gestore esterno». Limite dimensione frame — 256 KiB.

È possibile autenticarsi in uno dei due modi seguenti.

1. Intestazione durante l'handshake (preferito). Trasmetti il token nell'intestazione Authorization:

Authorization: Bearer <token>

2. Frame di autenticazione. Se l'intestazione Bearer non viene trasmessa, entro 10 secondi dalla connessione invia come primo frame:

{ "type": "auth", "token": "<token>" }

Se entro 10 secondi il frame di autenticazione non arriva — la connessione viene chiusa con codice 4401. Un token errato in entrambi i metodi comporta anch'esso la chiusura con 4401.

Il token viene generato alla creazione dell'integrazione, mostrato una sola volta e conservato solo come hash (sha256); il confronto avviene in constant-time. Rotazione del token:

POST /api/bots/{botID}/integrations/{integrationID}/rotate-token

L'endpoint restituisce il nuovo token in chiaro; quello vecchio smette di funzionare immediatamente.

Eventi: bot → servizio

Ogni evento è un frame JSON (EventEnvelope). Il campo type è uno tra: session.open, message, callback, session.cancel, session.expired.

Set completo dei campi dell'envelope:

{
  "type": "session.open",
  "session_id": "string",
  "bot_id": "string",
  "user": {
    "tg_user_id": 123456789,
    "first_name": "Ivan",
    "username": "ivan",
    "params": { "utm": "promo" }
  },
  "chat": { "id": 123456789, "type": "private" },
  "update": { "...": "Telegram Update grezzo (raw JSON)" },
  "event": { "...": "evento normalizzato GetMyBot (raw JSON)" }
}
  • session_id — identificatore della sessione (dialogo).
  • bot_id — identificatore del bot.
  • user — dati dell'utente: tg_user_id (int64), first_name e username opzionali, più params — parametri utente (inclusi quelli del link), mappa chiave→valore.
  • chat — chat: id (int64) e type (ad esempio "private").
  • update — Telegram Update grezzo (raw JSON). Presente per session.open, message, callback.
  • event — evento normalizzato GetMyBot (raw JSON).

Il contenuto varia per tipo:

  • session.open — il trigger si è attivato, è stato aperto un nuovo dialogo. Contiene update (Update grezzo) e event (evento normalizzato completo), user.params è popolato.
  • message — arriva quando il servizio «tiene attiva l'attesa» (expect = text/any) e l'utente ha inviato un messaggio. Il contenuto è lo stesso di session.open.
  • callback — l'utente ha premuto un pulsante inviato in precedenza dal servizio. update è grezzo; in user è popolato solo tg_user_id; event contiene solo il callback_data originale impostato dal servizio nel pulsante:
{ "callback_data": "<valore originale impostato dal servizio nel pulsante>" }
  • session.cancel / session.expired — envelope minimo: session_id, bot_id, user.tg_user_id, chat.id (per cancel anche chat.type). I campi update/event non sono presenti. Questi eventi vengono consegnati solo se il servizio è online — non vengono inseriti nella coda offline.

Comandi: servizio → bot

Un comando è un frame JSON (CommandEnvelope). Il campo type è uno tra: execute, session.close, auth, ping, pong.

{
  "type": "execute",
  "session_id": "string",
  "methods": [
    { "method": "sendMessage", "params": { "text": "Ciao" } }
  ],
  "expect": "none"
}
  • session_id — la sessione a cui si riferisce il comando.
  • token — solo per type: "auth" (vedi Connessione e autorizzazione).
  • methods — array di oggetti { "method": "<nome del metodo Telegram Bot API>", "params": { ... } }. Il nome del metodo è esattamente come nel Telegram Bot API (ad esempio sendMessage), params è l'oggetto dei parametri di quel metodo.
  • expect — se il servizio attende una risposta dell'utente: "none", "text" o "any" (un valore vuoto viene interpretato come none).
  • session.close — chiudi esplicitamente il dialogo.

I comandi non vanno direttamente a Telegram: GetMyBot li valida e li inserisce nel proprio outbox, da dove li invia con il token del bot (con rate-limit, tentativi, deduplicazione e registro).

Come viene elaborato execute

Il comando execute viene scartato integralmente se: session_id è vuoto; la sessione non viene trovata o non è in stato open; la sessione appartiene a un'altra integrazione; la sessione è scaduta.

Successivamente:

  • Rate-limit: fino a 60 execute al minuto per sessione (finestra scorrevole). Oltre il limite — scartato.
  • Fino a 30 metodi in un singolo execute; i metodi in eccesso vengono silenziosamente scartati.
  • Per ogni metodo viene verificata la whitelist (vedi Metodi consentiti) — un metodo non presente nell'elenco viene rifiutato.
  • Chat-guard: se in params è presente chat_id o from_chat_id con un valore diverso da 0 e diverso dal chat_id della sessione — l'intero metodo viene rifiutato (non è possibile inviare a chat altrui). chat_id può essere omesso del tutto: GetMyBot imposterà forzatamente la chat della sessione.
  • I pulsanti inline con callback_data vengono automaticamente tokenizzati (formato interno x:<token>); la durata del token del pulsante è fino alla scadenza della sessione. I pulsanti url/webapp/switch_inline non vengono modificati.

Metodi consentiti

Il servizio comanda il bot, quindi l'insieme dei metodi è limitato a una whitelist — solo invio e gestione dei messaggi nel dialogo corrente. I metodi non presenti nell'elenco vengono silenziosamente ignorati (non è un errore).

  • invio: sendMessage, sendPhoto, sendDocument, sendVideo, sendAudio, sendMediaGroup, sendAnimation, sendVoice, sendLocation, sendChatAction;
  • modifica ed eliminazione: editMessageText, editMessageCaption, editMessageReplyMarkup, deleteMessage;
  • risposta alla pressione: answerCallbackQuery;
  • inoltro e copia: forwardMessage, copyMessage;
  • blocco: pinChatMessage, unpinChatMessage.

I metodi a livello di account (setWebhook, getUpdates, logOut, close, setMyCommands ecc.) non sono consentiti.

Attesa e ciclo di vita della sessione

Dopo ogni execute il destino della sessione dipende da expect e dalla presenza di pulsanti inline con callback_data nel comando:

  • expect = text o any — il successivo messaggio dell'utente verrà inoltrato al servizio come evento message.
  • expect = none E nel comando NON ci sono pulsanti inline con callback — la sessione viene chiusa automaticamente. Si tratta di un messaggio «terminale».
  • expect = none, MA sono presenti pulsanti callback — la sessione rimane aperta: le pressioni vengono catturate finché i token dei pulsanti sono validi (fino al TTL).

Importante: expect non influisce sulla ricezione delle pressioni dei pulsanti. Le pressioni di pulsanti tokenizzati vengono catturate indipendentemente dal valore di expect — finché la sessione e il token del pulsante sono validi. expect controlla solo se il servizio attende una risposta testuale/arbitraria.

Il contesto del dialogo è attivo esattamente finché la sessione è aperta:

  • Risposta testuale con attesa attiva (expect = text/any) — viene inviata al servizio come evento message, non attiva le normali reazioni del bot.
  • Pressione di un pulsante tokenizzato — viene inviata al servizio come evento callback con il callback_data originale. GetMyBot conferma sempre la pressione (answerCallbackQuery); viene verificata l'appartenenza della pressione (bot + utente + chat della sessione). La pressione del pulsante non chiude la sessione.
  • /cancel (nonché /cancel@bot e «annulla») con una sessione esterna attiva — la sessione viene chiusa, al servizio viene inviato session.cancel, all'utente viene inviato «Annullato.».
  • Un dialogo per utente. L'apertura di una nuova sessione sostituisce la precedente sessione aperta di quell'utente — a quest'ultima viene inviato session.cancel.
  • Scadenza TTL. Allo scadere del timer la sessione viene contrassegnata come expired e (se il servizio è online) al servizio viene inviato session.expired.

Limiti e timeout

  • Dimensione frame: 256 KiB.
  • TTL sessione: 3600 secondi per impostazione predefinita (configurabile con il parametro session_ttl_seconds dell'integrazione).
  • Frequenza comandi: 60 execute al minuto per sessione (finestra scorrevole).
  • Metodi in un singolo execute: fino a 30 (i metodi in eccesso vengono scartati).
  • Sessioni aperte per integrazione: fino a 1000.
  • Coda offline: fino a 100 eventi per sessione.
  • Heartbeat: ping ogni 30 secondi; timeout per assenza di attività — 60 secondi.
  • Timeout frame di autenticazione: 10 secondi.

Affidabilità (heartbeat, offline, coda, fallback)

  • Heartbeat. Il server invia un frame {"type":"ping"} ogni 30 secondi. Se dal client non arriva attività per più di 60 secondi — la connessione viene chiusa. Il client può inviare i propri ping/pong per mantenere l'attività. Un'interruzione della connessione non elimina i dialoghi: le sessioni restano nel database fino al TTL o alla riconnessione.
  • Riconnessione — a carico del servizio. Se la connessione si interrompe, il tuo servizio deve riconnettersi. Le sessioni aperte nel database sopravvivono ai riavvii fino alla scadenza del TTL.
  • Fallback in caso di offline all'avvio. Se al momento dell'attivazione della reazione il servizio non è connesso — viene avviata la reazione di riserva dalle impostazioni dell'integrazione (on_unavailable_reaction_id). Se non è configurata — non accade nulla (silenziosamente).
  • Coda offline. Gli eventi message/callback non consegnati a causa dell'offline vengono inseriti nella coda offline (fino a 100 eventi, FIFO, conservati fino alla scadenza della sessione) e inviati alla riconnessione. Gli eventi session.open/session.cancel/session.expired non vengono inseriti nella coda.
  • Garanzia di consegna — at-most-once. In caso di overflow o scadenza della coda, gli eventi vengono scartati. Progetta la logica in modo che la perdita di un singolo evento non comprometta lo scenario.

Indicatore online

Per verificare se il servizio mantiene una connessione attiva, puoi usare l'endpoint:

GET /api/bots/{botID}/integrations/{integrationID}/status

Risposta:

{ "online": true }

Lo stesso indicatore è disponibile sulla scheda dell'integrazione nel pannello di controllo. L'equivalente in MCP è lo strumento get_integration_status.

Sicurezza

GetMyBot esegue i comandi di un servizio esterno con il proprio token, quindi la protezione è rigorosa.

  • Nessuna superficie SSRF. GetMyBot funge da server WebSocket e non effettua connessioni in uscita verso il servizio — è il servizio a connettersi a GetMyBot. Non esiste alcun URL controllato dall'esterno verso cui la piattaforma si connette.
  • Il token è conservato come hash (sha256), il confronto avviene in constant-time, la rotazione è supportata.
  • Isolamento del tenant. Un comando è strettamente legato alla propria integrazione; una sessione è legata al bot + utente + chat. Gli eventi di bot altrui non entrano nel tuo socket.
  • Whitelist dei metodi + chat_id forzato impediscono di trasformare il bot in uno strumento di invio verso chat arbitrarie.
  • Il token del bot non viene trasmesso all'esterno — il servizio non comunica mai direttamente con Telegram.
  • Mascheramento dei segreti. Il token dell'integrazione e i valori sensibili vengono oscurati nel registro e nei log.

Esempi

Gestore minimo: all'apertura del dialogo invia un messaggio e chiude la sessione (expect: "none"). Sostituisci l'id dell'integrazione e il token. L'indirizzo deve contenere obbligatoriamente <integrationID> nel percorso.

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: "Ciao dal gestore esterno!" } }],
    }));
  }
});

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": "Ciao dal gestore esterno!"}}
                    ],
                }))

asyncio.run(main())

Pulsante inline e gestione del callback

Per catturare una pressione, invia un pulsante con callback_data (GetMyBot lo tokenizza automaticamente). Con expect: "none" e un pulsante la sessione rimane aperta finché il token del pulsante è valido — la pressione arriverà come evento callback, in cui event.callback_data è uguale al valore originale. Non è necessario confermare la pressione (answerCallbackQuery) — GetMyBot lo fa automaticamente.

if (ev.type === "session.open") {
  ws.send(JSON.stringify({
    type: "execute",
    session_id: ev.session_id,
    expect: "none",
    methods: [{
      method: "sendMessage",
      params: {
        text: "Premi il pulsante",
        reply_markup: { inline_keyboard: [[{ text: "Vai", 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: "Pulsante premuto!" } }],
  }));
}

Prossimi passi

  • Integrazioni — panoramica generale dei servizi collegabili.
  • Reazioni — come si costruiscono trigger, condizioni e azioni.
  • Richieste web e webhook — un modo più semplice per richiamare un URL esterno senza dialogo.