外部處理器
外部處理器是你的程式碼與 Telegram 之間的橋樑。當建構器的邏輯不夠用時,你可以將特定反應交給自己的伺服器處理:伺服器接收事件並以 Telegram Bot API 格式回覆指令,而 GetMyBot 負責傳輸、投遞以及所有 Telegram 相關操作。
這是什麼
GetMyBot 中的反應以視覺化方式組建:觸發器 → 條件 → 動作。複雜或非標準的邏輯——自有資料庫、計算、呼叫第三方系統、ML、分支場景——在建構器中難以表達。外部處理器突破了這個上限:你連接自己的後端,並將整個反應的處理權交給它。
外部服務從 GetMyBot 接收事件(觸發器觸發、用戶回覆文字、點擊按鈕),並以指令告訴機器人要傳送什麼。GetMyBot 用自己的機器人 token 執行這些指令——在此之上有速率限制、重試、去重以及對話日誌。
運作模型
- GetMyBot 是 WebSocket 伺服器。 你的服務主動連接到它,不需要公開地址或 webhook 端點。GetMyBot 不會主動向你的服務發起連線,因此不存在 SSRF 攻擊面。
- 認證使用整合 token。 服務在連接時提供 token;GetMyBot 與其雜湊值進行比對並保持連線。
- 事件與指令採用 Telegram Bot API 格式。 服務接收事件,並以「方法 + 參數」陣列的形式回覆,與操作普通機器人完全相同。
- 指令由 GetMyBot 用自己的 token 執行。 服務從不直接與 Telegram 通訊:指令經過驗證後進入 GetMyBot 的內部 outbox,再由機器人 token 傳送(帶速率限制、重試、去重與日誌)。
一個連線服務一個整合的所有對話——不同用戶的事件透過多路複用區分,以 session_id 欄位識別。
設定整合
- 開啟左側選單中的整合區塊,點選新增整合。
- 選擇外部處理器服務,並設定名稱。
- 建立後,卡片會顯示:
- WebSocket 地址 — 格式為
wss://api.mybot.app/ext/ws/<integrationID>,其中<integrationID>是此整合的 UUID。你的服務透過此地址連接。路徑中沒有 ID 則無法建立連線。 - 連接 token — 僅在建立時顯示一次。請複製並妥善儲存;token 只以雜湊形式存放,無法再次查看。
- 重新產生 token — 此按鈕會產生新 token 並立即使舊 token 失效。重新產生後,請在你的服務中更新 token。
- 上線狀態指示器 — 顯示你的服務目前是否保持活躍連線。
- 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. 握手時的標頭(建議)。 在 Authorization 標頭中傳遞 token:
Authorization: Bearer <token>
2. Auth 訊框。 若未傳遞 Bearer 標頭,請在連線建立後的 10 秒內傳送第一個訊框:
{ "type": "auth", "token": "<token>" }
若 10 秒內未收到 auth 訊框,連線將以代碼 4401 關閉。任何方式中的無效 token 同樣以 4401 關閉。
Token 在建立整合時產生,僅顯示一次,以 sha256 雜湊形式存放,比對時使用常數時間比較。Token 輪換:
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
端點返回新的明文 token;舊 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": { "...": "raw Telegram Update (raw JSON)" },
"event": { "...": "normalized GetMyBot event (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": "Hello" } }
],
"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,再由機器人 token 傳送(帶速率限制、重試、去重與日誌)。
execute 的處理方式
在以下情況下,execute 指令會被整體丟棄:session_id 為空;會話未找到或狀態不是 open;會話屬於其他整合;會話已過期。
之後:
- 速率限制: 每個會話每分鐘最多 60 個
execute(滑動視窗)。超過限制則丟棄。 - 每個
execute最多 30 個方法;多餘的會被靜默丟棄。 - 每個方法都會檢查白名單(請參閱允許的方法)——不在清單中的方法會被拒絕。
- 聊天防護: 如果
params中的chat_id或from_chat_id的值不為0且不等於會話的chat_id,則整個方法被拒絕(不能向其他聊天傳送訊息)。chat_id可以不指定:GetMyBot 會強制填入當前會話的聊天。 - 帶有
callback_data的 inline 按鈕會自動 token 化(內部格式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 按鈕 — 會話自動關閉。這是「終止」訊息。expect=none,但有 callback 按鈕 — 會話保持開放:點擊事件會被捕捉,直到按鈕 token 的存活時間(TTL)到期。
重要:expect 不影響按鈕點擊的接收。 token 化按鈕的點擊與 expect 的值無關——只要會話和按鈕 token 有效就會被捕捉。expect 只控制服務是否等待文字/任意回覆。
對話邊界
對話上下文的存活時間恰好與會話開放的時間一樣長:
- 活躍等待中的文字回覆(
expect=text/any)——以message事件傳給服務,而不是觸發機器人的普通反應。 - token 化按鈕的點擊——以
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 個事件。
- Heartbeat: 每 30 秒一個
ping;無活動逾時為 60 秒。 - Auth 訊框逾時: 10 秒。
可靠性(heartbeat、離線、佇列、fallback)
- Heartbeat。 伺服器每 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 使用自己的 token 執行第三方服務的指令,因此安全保護非常嚴格。
- 無 SSRF 攻擊面。 GetMyBot 作為 WebSocket 伺服器,不主動向服務發起連線——是服務連接到 GetMyBot。平台不會訪問任何由外部控制的 URL。
- Token 以雜湊形式存放(sha256),比對使用常數時間,支援輪換。
- 租戶隔離。 指令嚴格綁定到所屬整合;會話綁定到機器人 + 用戶 + 聊天。其他機器人的事件不會進入你的 socket。
- 方法白名單 + 強制
chat_id防止機器人被用作向任意聊天發送訊息的工具。 - 機器人 token 不對外暴露 — 服務從不直接與 Telegram 通訊。
- 敏感資訊遮蔽。 整合 token 和敏感值在日誌中會被遮蔽。
範例
最小處理器:在對話開啟時傳送訊息並關閉會話(expect: "none")。請替換整合 ID 和 token。地址路徑中必須包含 <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: "Hello from external handler!" } }],
}));
}
});
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": "Hello from external handler!"}}
],
}))
asyncio.run(main())
Inline 按鈕與 callback 處理
要捕捉點擊,請傳送帶有 callback_data 的按鈕(GetMyBot 會自動 token 化)。使用帶有按鈕的 expect: "none" 時,會話保持開放,直到按鈕 token 存活時間到期——點擊會以 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: "Click the button",
reply_markup: { inline_keyboard: [[{ text: "Go", 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: "Button clicked!" } }],
}));
}
下一步
- 整合 — 可連接服務的總覽。
- 反應 — 觸發器、條件與動作的組合方式。
- Web 請求與 webhook — 不需要對話時呼叫外部 URL 的更簡單方式。