外部处理器
外部处理器是你的代码与 Telegram 之间的桥梁。当构建器的逻辑不够用时,你可以将某个反应交给自己的服务器处理:它接收事件并以 Telegram Bot API 格式回传命令,而 GetMyBot 负责传输、消息投递以及所有 Telegram 通信。
简介
GetMyBot 中的反应通过可视化方式搭建:触发器 → 条件 → 动作。但对于复杂或非标准逻辑——自有数据库、计算、调用第三方系统、ML、分支复杂的场景——在构建器中难以表达。外部处理器打破了这一上限:你接入自己的后端,并将整个反应的处理交给它。
外部服务从 GetMyBot 接收事件(触发器触发、用户回复文本、点击按钮),并命令机器人发送内容。GetMyBot 用自己的机器人令牌执行命令——在此之上运行着速率限制、重试、去重和对话日志。
工作模型
- GetMyBot 是 WebSocket 服务器。 你的服务主动连接到它,无需公网地址或 webhook 端点。GetMyBot 不向你的服务发起任何出站连接,因此没有 SSRF 攻击面。
- 通过集成令牌认证。 服务在连接时提交令牌;GetMyBot 与哈希值比对后保持连接。
- 事件与命令采用 Telegram Bot API 格式。 服务接收事件,并以"方法 + 参数"数组的形式回复,与操作普通机器人完全一致。
- 命令由 GetMyBot 用自己的令牌执行。 服务从不直接与 Telegram 通信:命令经过验证后进入 GetMyBot 的内部 outbox,由机器人令牌以速率限制、重试、去重和日志记录的方式发送。
一个连接服务于同一集成的所有对话——不同用户的事件在同一连接中多路复用,通过 session_id 字段区分。
配置集成
- 打开左侧菜单的集成,点击添加集成。
- 选择外部处理器服务并设置名称。
- 创建后,卡片将显示:
- WebSocket 地址 — 格式为
wss://api.mybot.app/ext/ws/<integrationID>,其中<integrationID>是此集成的 UUID。你的服务通过该地址连接。路径中缺少 id 则无法建立连接。 - 连接令牌 — 仅在创建时显示一次。请复制并妥善保存;令牌只以哈希形式存储,无法再次查看。
- 重新生成令牌 — 该按钮生成新令牌并立即使旧令牌失效。重新生成后请在你的服务中更新令牌。
- 在线状态指示器 — 显示你的服务当前是否保持活跃连接。
- WebSocket 地址 — 格式为
影响协议的集成附加参数:
session_ttl_seconds— 会话生存时间(秒),默认3600。on_unavailable_reaction_id— 备用反应:服务离线时触发(详见可靠性)。
绑定到反应
外部处理器不通过独立触发器接入,而是通过动作接入。在反应编辑器中添加转交外部处理器动作并选择对应集成。
任何现有的 GetMyBot 触发器都可以开启对话——命令、文本、按钮点击、链接参数、传入 Web 请求、定时任务。当此类触发器触发并到达该动作时,GetMyBot 会开启代理会话并向服务发送 session.open 事件。
如果此时服务未连接,GetMyBot 将启动集成设置中的备用反应(on_unavailable_reaction_id)。若未设置备用反应——什么都不会发生(静默,用户不会收到错误)。
连接与认证
端点:GET /ext/ws/{integrationID},基于 WebSocket(wss)。integrationID 是"外部处理器"类型集成的 UUID。帧大小上限为 256 KiB。
认证方式有两种。
1. 握手时通过 Header 传入(推荐)。 在 Authorization 头中传递令牌:
Authorization: Bearer <token>
2. 通过认证帧传入。 若未传入 Bearer 头,请在建立连接后 10 秒内发送第一帧:
{ "type": "auth", "token": "<token>" }
若 10 秒内未收到认证帧,连接以 4401 关闭。任何方式下令牌无效均以 4401 关闭。
令牌在集成创建时生成,仅显示一次,以哈希(sha256)形式存储,比对采用恒定时间算法。令牌轮换:
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
该端点返回新的明文令牌;旧令牌立即失效。
事件:机器人 → 服务
每个事件是一个 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": "Ivan",
"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——用户参数(含链接参数),键值对映射。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,再以机器人令牌发送(含速率限制、重试、去重、日志)。
execute 的处理方式
execute 命令在以下情况下整体丢弃:session_id 为空;会话未找到或状态不为 open;会话属于其他集成;会话已过期。
之后:
- 速率限制: 每个会话每分钟最多 60 个
execute(滑动窗口)。超出限制则丢弃。 - 单个
execute最多 30 个方法;多余的静默丢弃。 - 每个方法都检查白名单(详见允许的方法)——不在列表中的方法被拒绝。
- 聊天守卫: 若
params中的chat_id或from_chat_id值不为0且不等于会话的chat_id,则整个方法被拒绝(不允许向其他聊天发送)。可以不填chat_id:GetMyBot 会自动填入会话的聊天。 - 带
callback_data的 inline 按钮会自动令牌化(内部格式x:<token>);按钮生存时间 = 会话 TTL。url/webapp/switch_inline类型按钮不受影响。
允许的方法
服务通过机器人令牌下发命令,因此方法集合受白名单限制——仅限发送消息及操作当前对话。不在列表中的方法静默忽略(不视为错误)。
- 发送:
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 以及命令中是否包含带 callback_data 的 inline 按钮:
expect=text或any— 用户的下一条消息将以message事件转发给服务。expect=none且命令中无 callback inline 按钮 — 会话自动关闭。这是"终止"消息。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 个事件。
- 心跳: 每 30 秒发送一次
ping;无活动超时 60 秒。 - 认证帧超时: 10 秒。
可靠性(心跳、离线、队列、fallback)
- 心跳。 服务器每 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),比对采用恒定时间算法,支持令牌轮换。
- 租户隔离。 命令严格绑定到自身集成;会话绑定到机器人 + 用户 + 聊天。其他机器人的事件不会进入你的 socket。
- 方法白名单 + 强制
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: "按钮已点击!" } }],
}));
}
下一步
- 集成 — 可接入服务的总览。
- 反应 — 触发器、条件和动作的组合方式。
- Web 请求与 webhook — 无需对话即可调用外部 URL 的更简单方式。