Manipulador externo

O manipulador externo é uma ponte entre seu código e o Telegram. Quando a lógica do construtor não é suficiente, você delega uma reação específica ao seu servidor: ele recebe eventos e responde com comandos no formato da Telegram Bot API, enquanto o GetMyBot cuida do transporte, da entrega e de toda a comunicação com o Telegram.

O que é isso

As reações no GetMyBot são montadas visualmente: gatilho → condições → ações. Lógica complexa ou não convencional — sua própria base de dados, cálculos, chamadas a sistemas externos, ML, cenários ramificados — é difícil de expressar no construtor. O manipulador externo remove esse limite: você conecta seu próprio backend e delega a ele o processamento de reações inteiras.

O serviço externo recebe do GetMyBot eventos (gatilho acionado, usuário respondeu com texto, clicou em botão) e, em resposta, instrui o bot sobre o que enviar. O GetMyBot executa os comandos com seu próprio token de bot — por cima funcionam rate-limit, retentativas, deduplicação e histórico de diálogo.

Modelo de funcionamento

  • GetMyBot é o servidor WebSocket. Seu serviço se conecta a ele e não precisa de um endereço público nem de um endpoint de webhook. O GetMyBot não faz conexões de saída para o seu serviço, portanto não há superfície de ataque SSRF.
  • Autenticação por token de integração. O serviço apresenta o token ao conectar; o GetMyBot o verifica com o hash e mantém a conexão.
  • Eventos e comandos no formato da Telegram Bot API. O serviço recebe um evento e responde com um array de chamadas no formato "método + parâmetros", exatamente como ao trabalhar com um bot comum.
  • Os comandos são executados pelo GetMyBot com seu próprio token. O serviço nunca se comunica com o Telegram diretamente: os comandos passam por validação e entram no outbox interno do GetMyBot, de onde são enviados com o token do bot com rate-limit, retentativas, deduplicação e histórico.

Uma única conexão atende a todos os diálogos de uma integração — eventos de diferentes usuários são multiplexados e distinguidos pelo campo session_id.

Configurar a integração

  1. Abra a seção Integrações no menu à esquerda e clique em Adicionar integração.
  2. Escolha o serviço Manipulador externo e defina um nome.
  3. Após a criação, o cartão exibirá:
    • Endereço WebSocket — no formato wss://api.mybot.app/ext/ws/<integrationID>, onde <integrationID> é o UUID desta integração. É por esse endereço que seu serviço se conecta. Sem o id no caminho, a conexão não será estabelecida.
    • Token de conexão — exibido uma única vez na criação. Copie e guarde em local seguro; não é possível visualizar o token novamente (ele é armazenado apenas como hash).
    • Rotacionar token — o botão gera um novo token e invalida imediatamente o anterior. Após a rotação, atualize o token no seu serviço.
    • Indicador online — mostra se o seu serviço mantém uma conexão ativa no momento.

Parâmetros adicionais da integração que afetam o protocolo:

  • session_ttl_seconds — tempo de vida da sessão em segundos (padrão: 3600).
  • on_unavailable_reaction_id — reação de fallback que é iniciada se o serviço estiver offline no momento do acionamento (veja Confiabilidade).

Vinculação a uma reação

O manipulador externo é conectado não como um gatilho separado, mas como uma ação. No editor de reação, adicione a ação Transferir para manipulador externo e selecione a integração desejada.

O diálogo pode ser iniciado por qualquer gatilho existente do GetMyBot — comando, texto, clique em botão, parâmetro de link, requisição web de entrada, agendamento. Quando esse gatilho é acionado e chega à ação, o GetMyBot abre uma sessão proxy e envia ao serviço o evento session.open.

Se nesse momento o serviço não estiver conectado, o GetMyBot iniciará a reação de fallback definida nas configurações da integração (on_unavailable_reaction_id). Se nenhuma reação de fallback estiver definida, nada acontecerá (silenciosamente, sem erro para o usuário).

Conexão e autorização

Endpoint: GET /ext/ws/{integrationID} sobre WebSocket (wss). integrationID é o UUID da integração do tipo "Manipulador externo". Limite de tamanho de frame: 256 KiB.

A autorização pode ser feita de duas maneiras.

1. Cabeçalho no handshake (preferencial). Passe o token no cabeçalho Authorization:

Authorization: Bearer <token>

2. Frame de auth. Se o cabeçalho Bearer não for enviado, envie como primeiro frame dentro de 10 segundos após a conexão ser estabelecida:

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

Se o frame de auth não chegar em 10 segundos, a conexão será fechada com código 4401. Um token inválido em qualquer uma das formas também resulta em close 4401.

O token é gerado na criação da integração, exibido uma única vez e armazenado apenas como hash (sha256); a verificação é feita em tempo constante. Rotação do token:

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

O endpoint retorna o novo token em texto simples; o anterior para de funcionar imediatamente.

Eventos: bot → serviço

Cada evento é um frame JSON (EventEnvelope). O campo type é um de: session.open, message, callback, session.cancel, session.expired.

Conjunto completo de campos do 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": { "...": "raw Telegram Update (raw JSON)" },
  "event": { "...": "evento normalizado do GetMyBot (raw JSON)" }
}
  • session_id — identificador da sessão (diálogo).
  • bot_id — identificador do bot.
  • user — dados do usuário: tg_user_id (int64), first_name e username opcionais, além de params — parâmetros do usuário (incluindo os de link), map chave→valor.
  • chat — chat: id (int64) e type (por exemplo "private").
  • update — Telegram Update bruto (raw JSON). Presente em session.open, message, callback.
  • event — evento normalizado do GetMyBot (raw JSON).

O conteúdo varia por tipo:

  • session.open — gatilho acionado, novo diálogo aberto. Inclui update (Update bruto) e event (evento normalizado completo); user.params preenchidos.
  • message — enviado quando o serviço está "aguardando" (expect = text/any) e o usuário enviou uma mensagem. Mesmo conteúdo que session.open.
  • callback — o usuário clicou em um botão enviado anteriormente pelo serviço. update é bruto; em user apenas tg_user_id é preenchido; event contém apenas o callback_data original definido pelo serviço no botão:
{ "callback_data": "<valor original definido pelo serviço no botão>" }
  • session.cancel / session.expired — envelope mínimo: session_id, bot_id, user.tg_user_id, chat.id (para cancel, também chat.type). Campos update/event ausentes. Esses eventos são entregues apenas se o serviço estiver online — não são colocados na fila offline.

Comandos: serviço → bot

Um comando é um frame JSON (CommandEnvelope). O campo type é um de: execute, session.close, auth, ping, pong.

{
  "type": "execute",
  "session_id": "string",
  "methods": [
    { "method": "sendMessage", "params": { "text": "Olá" } }
  ],
  "expect": "none"
}
  • session_id — a sessão à qual o comando pertence.
  • token — apenas para type: "auth" (veja Conexão e autorização).
  • methods — array de objetos { "method": "<nome do método da Telegram Bot API>", "params": { ... } }. O nome do método é exatamente como na Telegram Bot API (por exemplo sendMessage); params é o objeto de parâmetros desse método.
  • expect — se o serviço aguarda resposta do usuário: "none", "text" ou "any" (valor vazio é tratado como none).
  • session.close — fecha explicitamente o diálogo.

Os comandos não vão ao Telegram diretamente: o GetMyBot os valida e os coloca no outbox interno, de onde são enviados com o token do bot (com rate-limit, retentativas, deduplicação e histórico).

Como o execute é processado

O comando execute é descartado inteiramente se: session_id estiver vazio; a sessão não for encontrada ou não estiver com status open; a sessão pertencer a outra integração; a sessão estiver expirada.

Em seguida:

  • Rate-limit: até 60 execute por minuto por sessão (janela deslizante). Acima do limite — descartado.
  • Até 30 métodos em um único execute; os excedentes são silenciosamente ignorados.
  • Cada método é verificado contra a lista de permissões (veja Métodos permitidos) — métodos fora da lista são rejeitados.
  • Chat-guard: se params contiver chat_id ou from_chat_id com valor diferente de 0 e diferente do chat_id da sessão, o método inteiro é rejeitado (não é permitido enviar para outro chat). chat_id pode ser omitido: o GetMyBot preencherá automaticamente com o chat da sessão.
  • Botões inline com callback_data são tokenizados automaticamente (formato interno x:<token>); o tempo de vida do botão é até o fim da sessão. Botões url/webapp/switch_inline não são alterados.

Métodos permitidos

O serviço comanda o bot, por isso o conjunto de métodos é limitado a uma lista de permissões — apenas envio e manipulação de mensagens do diálogo atual. Métodos fora da lista são silenciosamente ignorados (não é um erro).

  • envio: sendMessage, sendPhoto, sendDocument, sendVideo, sendAudio, sendMediaGroup, sendAnimation, sendVoice, sendLocation, sendChatAction;
  • edição e exclusão: editMessageText, editMessageCaption, editMessageReplyMarkup, deleteMessage;
  • resposta a clique: answerCallbackQuery;
  • encaminhamento e cópia: forwardMessage, copyMessage;
  • fixar/desafixar: pinChatMessage, unpinChatMessage.

Métodos de nível de conta (setWebhook, getUpdates, logOut, close, setMyCommands etc.) não são permitidos.

Aguardo e ciclo de vida da sessão

Após cada execute, o destino da sessão depende de expect e de se havia botões inline com callback_data no comando:

  • expect = text ou any — a próxima mensagem do usuário será encaminhada ao serviço como evento message.
  • expect = none E o comando NÃO tinha botões inline com callback — a sessão é fechada automaticamente. Esta é a mensagem "terminal".
  • expect = none, MAS há botões com callback — a sessão permanece aberta: os cliques são capturados enquanto os tokens dos botões estiverem vivos (até o TTL).

Importante: expect não afeta o recebimento de cliques em botões. Cliques em botões tokenizados são capturados independentemente do valor de expect — enquanto a sessão e o token do botão estiverem vivos. expect controla apenas se o serviço aguarda uma resposta de texto/qualquer tipo.

O contexto do diálogo existe exatamente enquanto a sessão estiver aberta:

  • Resposta de texto com aguardo ativo (expect = text/any) — vai ao serviço como evento message, sem acionar as reações normais do bot.
  • Clique em botão tokenizado — vai ao serviço como evento callback com o callback_data original. O GetMyBot sempre confirma o clique automaticamente (answerCallbackQuery); verifica-se o pertencimento do clique (bot + usuário + chat da sessão). Um clique em botão não fecha a sessão.
  • /cancel (também /cancel@bot e "cancelar") durante uma sessão externa ativa — a sessão é fechada, o serviço recebe session.cancel e o usuário recebe "Cancelado.".
  • Um diálogo por usuário. Abrir uma nova sessão substitui a sessão anterior aberta desse usuário — ela recebe session.cancel.
  • Expiração do TTL. Por temporizador, a sessão é marcada como expired e (se o serviço estiver online) o serviço recebe session.expired.

Limites e timeouts

  • Tamanho do frame: 256 KiB.
  • TTL da sessão: padrão 3600 segundos (configurável pelo parâmetro session_ttl_seconds da integração).
  • Frequência de comandos: 60 execute por minuto por sessão (janela deslizante).
  • Métodos por execute: até 30 (os excedentes são descartados).
  • Sessões abertas por integração: até 1000.
  • Fila offline: até 100 eventos por sessão.
  • Heartbeat: ping a cada 30 segundos; timeout de inatividade — 60 segundos.
  • Timeout do frame de auth: 10 segundos.

Confiabilidade (heartbeat, offline, fila, fallback)

  • Heartbeat. O servidor envia um frame {"type":"ping"} a cada 30 segundos. Se não houver atividade do cliente por mais de 60 segundos, a conexão é fechada. O cliente pode enviar seus próprios ping/pong para manter a atividade. A queda de conexão não encerra os diálogos: as sessões vivem no banco de dados até o TTL ou a reconexão.
  • Reconexão — responsabilidade do serviço. Se a conexão cair, seu serviço deve se reconectar. Sessões abertas no banco de dados sobrevivem a reinicializações até o TTL expirar.
  • Fallback quando offline na partida. Se no momento do acionamento da reação o serviço não estiver conectado — a reação de fallback das configurações da integração é iniciada (on_unavailable_reaction_id). Se não estiver definida — nada acontece (silenciosamente).
  • Fila offline. Eventos message/callback não entregues por causa do offline são colocados na fila offline (até 100 eventos, FIFO, armazenados até o fim da sessão) e entregues na reconexão. Eventos session.open/session.cancel/session.expired não são colocados na fila.
  • Garantia de entrega — at-most-once. Em caso de overflow ou expiração da fila, os eventos são descartados. Projete sua lógica de forma que a perda de um evento individual não quebre o cenário.

Indicador online

Para verificar se o serviço mantém uma conexão ativa, use o endpoint:

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

Resposta:

{ "online": true }

O mesmo indicador está disponível no cartão da integração no painel. O equivalente no MCP é a ferramenta get_integration_status.

Segurança

O GetMyBot executa comandos de um serviço externo com seu próprio token, por isso a proteção é rigorosa.

  • Sem superfície SSRF. O GetMyBot atua como servidor WebSocket e não faz conexões de saída para o serviço — é o serviço que se conecta ao GetMyBot. Não há URL controlada externamente para onde a plataforma faça requisições.
  • Token armazenado como hash (sha256), verificação em tempo constante, rotação suportada.
  • Isolamento de tenant. O comando está rigidamente vinculado à sua integração; a sessão está vinculada ao bot + usuário + chat. Eventos de outros bots não chegam ao seu socket.
  • Lista de permissões de métodos + chat_id forçado impedem que o bot seja usado como remetente para chats arbitrários.
  • O token do bot não é exposto externamente — o serviço nunca se comunica com o Telegram diretamente.
  • Mascaramento de segredos. O token de integração e valores sensíveis são ocultados no histórico e nos logs.

Exemplos

Manipulador mínimo: ao abrir o diálogo, envia uma mensagem e fecha a sessão (expect: "none"). Substitua o id da integração e o token. O endereço deve obrigatoriamente conter <integrationID> no caminho.

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: "Olá do manipulador externo!" } }],
    }));
  }
});

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": "Olá do manipulador externo!"}}
                    ],
                }))

asyncio.run(main())

Botão inline e processamento de callback

Para capturar um clique, envie um botão com callback_data (o GetMyBot o tokeniza automaticamente). Com expect: "none" e um botão presente, a sessão permanece aberta enquanto o token do botão estiver vivo — o clique chegará como evento callback, no qual event.callback_data é igual ao valor original. Não é necessário confirmar o clique (answerCallbackQuery) — o GetMyBot faz isso automaticamente.

if (ev.type === "session.open") {
  ws.send(JSON.stringify({
    type: "execute",
    session_id: ev.session_id,
    expect: "none",
    methods: [{
      method: "sendMessage",
      params: {
        text: "Clique no botão",
        reply_markup: { inline_keyboard: [[{ text: "Vamos lá", 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: "Botão clicado!" } }],
  }));
}

O que vem a seguir