Manejador externo
El manejador externo es un puente entre tu código y Telegram. Cuando la lógica del constructor no es suficiente, delegas una reacción concreta a tu servidor: este recibe los eventos y responde con comandos en formato Telegram Bot API, mientras GetMyBot se encarga del transporte, la entrega y todo lo relacionado con Telegram.
Qué es
Las reacciones en GetMyBot se crean visualmente: disparador → condiciones → acciones. La lógica compleja o personalizada — tu propia base de datos, cálculos, llamadas a sistemas externos, ML, escenarios con muchas ramas — es difícil de expresar en el constructor. El manejador externo elimina ese techo: conectas tu propio backend y le delegas reacciones completas para que las procese.
El servicio externo recibe de GetMyBot los eventos (se activó un disparador, el usuario respondió con texto, pulsó un botón) y a su vez ordena al bot qué enviar. GetMyBot ejecuta los comandos con su propio token de bot — por encima funcionan el rate-limit, los reintentos, la deduplicación y el registro del diálogo.
Modelo de funcionamiento
- GetMyBot es el servidor WebSocket. Tu servicio se conecta a él y no necesita una dirección pública ni un endpoint de webhook. GetMyBot no realiza conexiones salientes a tu servicio, por lo que no hay superficie de SSRF.
- Autenticación mediante token de integración. El servicio presenta el token al conectarse; GetMyBot lo compara con el hash y mantiene la conexión.
- Eventos y comandos en formato Telegram Bot API. El servicio recibe un evento y responde con un array de llamadas del tipo «método + parámetros», exactamente igual que al trabajar con un bot normal.
- Los comandos los ejecuta GetMyBot con su token. El servicio nunca se comunica directamente con Telegram: los comandos pasan por validación y entran en el outbox interno de GetMyBot, desde donde se envían con el token del bot con rate-limit, reintentos, deduplicación y registro.
Una sola conexión atiende todos los diálogos de una integración — los eventos de distintos usuarios se multiplexan y se distinguen por el campo session_id.
Configuración de la integración
- Abre la sección Integraciones en el menú izquierdo y pulsa Añadir integración.
- Selecciona el servicio Manejador externo y dale un nombre.
- Tras crear la integración, la tarjeta mostrará:
- Dirección WebSocket — del tipo
wss://api.mybot.app/ext/ws/<integrationID>, donde<integrationID>es el UUID de esta integración. Tu servicio se conecta a esta dirección. Sin el id en la ruta la conexión no se establece. - Token de conexión — se muestra una sola vez al crearse. Cópialo y guárdalo en un lugar seguro; no es posible verlo de nuevo (se almacena solo como hash).
- Regenerar token — el botón genera un nuevo token e invalida el anterior de inmediato. Tras la regeneración, actualiza el token en tu servicio.
- Indicador de estado online — muestra si tu servicio mantiene una conexión activa en este momento.
- Dirección WebSocket — del tipo
Parámetros adicionales de la integración que afectan al protocolo:
session_ttl_seconds— tiempo de vida de la sesión en segundos (por defecto3600).on_unavailable_reaction_id— reacción de respaldo que se lanza si el servicio está desconectado en el momento de la activación (ver Fiabilidad).
Vinculación a una reacción
El manejador externo no se conecta como un disparador independiente, sino como una acción. En el editor de la reacción, añade la acción Transferir al manejador externo y selecciona la integración correspondiente.
El diálogo puede abrirse con cualquier disparador existente de GetMyBot — comando, texto, pulsación de botón, parámetro desde un enlace, solicitud web entrante, programación. Cuando ese disparador se activa y llega a la acción, GetMyBot abre una sesión proxy y envía al servicio el evento session.open.
Si en ese momento el servicio no está conectado, GetMyBot lanzará la reacción de respaldo configurada en la integración (on_unavailable_reaction_id). Si no se ha definido ninguna reacción de respaldo, no ocurrirá nada (silenciosamente, sin error para el usuario).
Conexión y autorización
Endpoint: GET /ext/ws/{integrationID} sobre WebSocket (wss). integrationID es el UUID de la integración de tipo «Manejador externo». El límite de tamaño de trama es 256 KiB.
Puedes autenticarte de una de estas dos formas.
1. Cabecera en el handshake (preferido). Pasa el token en la cabecera Authorization:
Authorization: Bearer <token>
2. Trama de autenticación. Si no se ha enviado la cabecera Bearer, envía como primera trama en los 10 segundos siguientes al establecimiento de la conexión:
{ "type": "auth", "token": "<token>" }
Si en 10 segundos no llega la trama de autenticación, la conexión se cierra con el código 4401. Un token incorrecto en cualquiera de los métodos también provoca el cierre 4401.
El token se genera al crear la integración, se muestra una sola vez y se almacena solo como hash (sha256); la verificación es en tiempo constante. Rotación del token:
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
El endpoint devuelve el nuevo token en texto plano; el anterior deja de funcionar de inmediato.
Eventos: bot → servicio
Cada evento es una trama JSON (EventEnvelope). El campo type es uno de: session.open, message, callback, session.cancel, session.expired.
Conjunto completo de campos del 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 (JSON sin procesar)" },
"event": { "...": "evento normalizado de GetMyBot (JSON sin procesar)" }
}
session_id— identificador de la sesión (diálogo).bot_id— identificador del bot.user— datos del usuario:tg_user_id(int64), opcionalesfirst_name,username, yparams— parámetros del usuario (incluyendo los del enlace), mapa clave→valor.chat— chat:id(int64) ytype(por ejemplo"private").update— raw Telegram Update (JSON sin procesar). Presente ensession.open,message,callback.event— evento normalizado de GetMyBot (JSON sin procesar).
El contenido varía según el tipo:
session.open— se activó el disparador, se abrió un nuevo diálogo. Incluyeupdate(raw Update) yevent(evento normalizado completo),user.paramsestá relleno.message— llega cuando el servicio «mantiene una espera» (expect=text/any) y el usuario envió un mensaje. El contenido es igual que el desession.open.callback— el usuario pulsó un botón que el servicio envió anteriormente.updatees el raw; enusersolo está rellenotg_user_id;eventcontiene solo el callback_data original definido por el servicio en el botón:
{ "callback_data": "<valor original definido por el servicio en el botón>" }
session.cancel/session.expired— envelope mínimo:session_id,bot_id,user.tg_user_id,chat.id(encanceltambiénchat.type). Los camposupdate/eventno están presentes. Estos eventos se entregan solo si el servicio está online — no se añaden a la cola offline.
Comandos: servicio → bot
Un comando es una trama JSON (CommandEnvelope). El campo type es uno de: execute, session.close, auth, ping, pong.
{
"type": "execute",
"session_id": "string",
"methods": [
{ "method": "sendMessage", "params": { "text": "Hola" } }
],
"expect": "none"
}
session_id— la sesión a la que pertenece el comando.token— solo paratype: "auth"(ver Conexión y autorización).methods— array de objetos{ "method": "<nombre del método de Telegram Bot API>", "params": { ... } }. El nombre del método es exactamente el de Telegram Bot API (por ejemplosendMessage);paramses el objeto de parámetros de ese método.expect— si el servicio espera respuesta del usuario:"none","text"o"any"(un valor vacío se interpreta comonone).session.close— cierra explícitamente el diálogo.
Los comandos no van directamente a Telegram: GetMyBot los valida y los coloca en su outbox, desde donde los envía con el token del bot (con rate-limit, reintentos, deduplicación y registro).
Cómo se procesa execute
El comando execute se descarta por completo si: session_id está vacío; la sesión no se encuentra o no está en estado open; la sesión pertenece a otra integración; la sesión ha expirado.
Después:
- Rate-limit: hasta 60
executepor minuto por sesión (ventana deslizante). Por encima del límite se descartan. - Hasta 30 métodos en un solo
execute; los sobrantes se descartan silenciosamente. - Cada método se verifica contra la lista blanca (ver Métodos permitidos) — un método que no esté en la lista se rechaza.
- Chat-guard: si en
paramshaychat_idofrom_chat_idcon un valor distinto de0y distinto delchat_idde la sesión, todo el método se rechaza (no se puede enviar a un chat ajeno).chat_idpuede omitirse: GetMyBot asignará forzosamente el chat de la sesión. - Botones inline con
callback_datase tokenizan automáticamente (formato internox:<token>); el tiempo de vida del botón es hasta que expire la sesión. Los botonesurl/webapp/switch_inlineno se modifican.
Métodos permitidos
El servicio controla el bot mediante comandos, por lo que el conjunto de métodos está limitado por una lista blanca — solo envío y gestión de mensajes del diálogo actual. Los métodos que no están en la lista se ignoran silenciosamente (no es un error).
- envío:
sendMessage,sendPhoto,sendDocument,sendVideo,sendAudio,sendMediaGroup,sendAnimation,sendVoice,sendLocation,sendChatAction; - edición y eliminación:
editMessageText,editMessageCaption,editMessageReplyMarkup,deleteMessage; - respuesta a pulsación:
answerCallbackQuery; - reenvío y copia:
forwardMessage,copyMessage; - fijación:
pinChatMessage,unpinChatMessage.
Los métodos a nivel de cuenta (setWebhook, getUpdates, logOut, close, setMyCommands, etc.) no están permitidos.
Espera y ciclo de vida de la sesión
Tras cada execute, el destino de la sesión depende de expect y de si el comando contenía botones inline con callback_data:
expect=textoany— el siguiente mensaje del usuario se reenviará al servicio como eventomessage.expect=noneY el comando NO tenía botones inline con callback — la sesión se cierra automáticamente. Este es un mensaje «terminal».expect=none, PERO hay botones con callback — la sesión permanece abierta: las pulsaciones se capturan mientras vivan los tokens de los botones (hasta el TTL).
Importante: expect no afecta a la recepción de pulsaciones de botones. Las pulsaciones de botones tokenizados se capturan independientemente del valor de expect — mientras la sesión esté viva y el token del botón no haya expirado. expect solo controla si el servicio espera una respuesta de texto o arbitraria.
Límites del diálogo
El contexto del diálogo vive exactamente mientras la sesión esté abierta:
- Respuesta de texto con espera activa (
expect=text/any) — va al servicio como eventomessage, en lugar de activar las reacciones normales del bot. - Pulsación de botón tokenizado — va al servicio como evento
callbackcon elcallback_dataoriginal. GetMyBot siempre confirma la pulsación por su cuenta (answerCallbackQuery); se verifica la pertenencia de la pulsación (bot + usuario + chat de la sesión). La pulsación de un botón no cierra la sesión. /cancel(también/cancel@boty «cancelar») con una sesión externa activa — la sesión se cierra, se envíasession.cancelal servicio y se envía «Cancelado.» al usuario.- Un diálogo por usuario. Abrir una nueva sesión desplaza la sesión abierta anterior de ese usuario — a esta se le envía
session.cancel. - Expiración del TTL. Cuando el temporizador expira, la sesión se marca como
expiredy (si el servicio está online) se envíasession.expiredal servicio.
Límites y tiempos de espera
- Tamaño de trama: 256 KiB.
- TTL de sesión: por defecto 3600 segundos (configurable con el parámetro
session_ttl_secondsde la integración). - Frecuencia de comandos: 60
executepor minuto por sesión (ventana deslizante). - Métodos en un solo
execute: hasta 30 (los sobrantes se descartan). - Sesiones abiertas por integración: hasta 1000.
- Cola offline: hasta 100 eventos por sesión.
- Heartbeat:
pingcada 30 segundos; tiempo de espera por inactividad — 60 segundos. - Tiempo de espera de la trama de autenticación: 10 segundos.
Fiabilidad (heartbeat, offline, cola, fallback)
- Heartbeat. El servidor envía una trama
{"type":"ping"}cada 30 segundos. Si no hay actividad del cliente durante más de 60 segundos, la conexión se cierra. El cliente puede enviar sus propiosping/pongpara mantener la actividad. Una desconexión no mata los diálogos: las sesiones viven en la base de datos hasta el TTL o la reconexión. - La reconexión es responsabilidad del servicio. Si la conexión se interrumpe, tu servicio debe reconectarse. Las sesiones abiertas en la base de datos sobreviven a los reinicios hasta que expire el TTL.
- Fallback cuando está offline al inicio. Si en el momento de activarse la reacción el servicio no está conectado, se lanza la reacción de respaldo configurada en la integración (
on_unavailable_reaction_id). Si no está definida, no ocurre nada (silenciosamente). - Cola offline. Los eventos
message/callbackque no se pudieron entregar por estar offline se almacenan en la cola offline (hasta 100 eventos, FIFO, se conserva hasta que expire la sesión) y se entregan al reconectarse. Los eventossession.open/session.cancel/session.expiredno se almacenan en la cola. - Garantía de entrega — at-most-once. Si la cola se desborda o caduca, los eventos se descartan. Diseña tu lógica de forma que la pérdida de un evento concreto no rompa el escenario.
Indicador de estado online
Para verificar si el servicio mantiene una conexión activa, usa el endpoint:
GET /api/bots/{botID}/integrations/{integrationID}/status
Respuesta:
{ "online": true }
El mismo indicador está disponible en la tarjeta de integración en el panel. El equivalente en MCP es la herramienta get_integration_status.
Seguridad
GetMyBot ejecuta los comandos de un servicio externo con su propio token, por lo que la protección es estricta.
- Sin superficie de SSRF. GetMyBot actúa como servidor WebSocket y no realiza conexiones salientes al servicio — es el servicio quien se conecta a GetMyBot. No existe ninguna URL controlada externamente a la que la plataforma acceda.
- El token se almacena como hash (sha256), la verificación es en tiempo constante y se admite la rotación.
- Aislamiento por inquilino. Un comando está estrictamente vinculado a su integración; la sesión, al bot + usuario + chat. Los eventos de bots ajenos no llegan a tu socket.
- Lista blanca de métodos +
chat_idforzado impiden convertir el bot en un remitente masivo a chats arbitrarios. - El token del bot no se expone externamente — el servicio nunca se comunica directamente con Telegram.
- Enmascaramiento de secretos. El token de integración y los valores sensibles se ocultan en el registro y en los logs.
Ejemplos
Manejador mínimo: al abrir el diálogo envía un mensaje y cierra la sesión (expect: "none"). Sustituye el id de integración y el token. La dirección debe contener obligatoriamente <integrationID> en la ruta.
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: "Hola desde el manejador 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": "Hola desde el manejador externo!"}}
],
}))
asyncio.run(main())
Botón inline y manejo de callback
Para capturar una pulsación, envía un botón con callback_data (GetMyBot lo tokeniza automáticamente). Con expect: "none" y un botón, la sesión permanece abierta mientras viva el token del botón — la pulsación llegará como evento callback en el que event.callback_data es igual al valor original. No es necesario confirmar la pulsación (answerCallbackQuery) — GetMyBot lo hace por su cuenta.
if (ev.type === "session.open") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{
method: "sendMessage",
params: {
text: "Pulsa el botón",
reply_markup: { inline_keyboard: [[{ text: "Vamos", 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ón pulsado!" } }],
}));
}
Qué sigue
- Integraciones — visión general de los servicios conectables.
- Reacciones — cómo se construyen disparadores, condiciones y acciones.
- Solicitudes web y webhooks — forma más sencilla de llamar a una URL externa sin diálogo.