Trình xử lý bên ngoài
Trình xử lý bên ngoài là cầu nối giữa code của bạn và Telegram. Khi logic trong bộ xây dựng không đủ, bạn chuyển giao một phản ứng riêng biệt cho server của mình: server nhận sự kiện và trả lời bằng các lệnh theo định dạng Telegram Bot API, còn GetMyBot đảm nhận toàn bộ phần truyền tải, giao nhận và tất cả Telegram.
Đây là gì
Các phản ứng trong GetMyBot được xây dựng trực quan: trình kích hoạt → điều kiện → hành động. Logic phức tạp hoặc phi tiêu chuẩn — cơ sở dữ liệu riêng, tính toán, tích hợp với hệ thống bên ngoài, ML, kịch bản phân nhánh — khó biểu đạt trong bộ xây dựng. Trình xử lý bên ngoài xóa bỏ giới hạn này: bạn kết nối backend riêng và chuyển giao toàn bộ phản ứng cho nó xử lý.
Dịch vụ bên ngoài nhận sự kiện từ GetMyBot (trình kích hoạt kích hoạt, người dùng trả lời văn bản, nhấn nút) và đổi lại ra lệnh cho bot phải gửi gì. GetMyBot thực thi các lệnh bằng token bot của mình — trên đó hoạt động rate-limit, thử lại, loại trùng lặp, nhật ký hội thoại.
Mô hình hoạt động
- GetMyBot là WebSocket-server. Dịch vụ của bạn tự kết nối đến nó và không cần địa chỉ công khai hay endpoint webhook. GetMyBot không thực hiện kết nối ra ngoài đến dịch vụ của bạn, vì vậy không có bề mặt SSRF.
- Xác thực bằng token tích hợp. Dịch vụ trình bày token khi kết nối; GetMyBot kiểm tra nó với hash và duy trì kết nối.
- Sự kiện và lệnh theo định dạng Telegram Bot API. Dịch vụ nhận sự kiện và trả lời bằng mảng các lệnh theo dạng "phương thức + tham số", giống hệt như khi làm việc với bot thông thường.
- Lệnh được thực thi bởi GetMyBot bằng token của nó. Dịch vụ không bao giờ giao tiếp trực tiếp với Telegram: các lệnh được xác thực và đưa vào outbox nội bộ của GetMyBot, từ đó được gửi bằng token bot với rate-limit, thử lại, loại trùng lặp và nhật ký.
Một kết nối phục vụ tất cả hội thoại của một tích hợp — các sự kiện của nhiều người dùng được ghép kênh và phân biệt bằng trường session_id.
Cấu hình tích hợp
- Mở phần Tích hợp trong menu bên trái và nhấn Thêm tích hợp.
- Chọn dịch vụ Trình xử lý bên ngoài và đặt tên.
- Sau khi tạo, thẻ sẽ hiển thị:
- Địa chỉ WebSocket — dạng
wss://api.mybot.app/ext/ws/<integrationID>, trong đó<integrationID>— UUID của tích hợp này. Dịch vụ của bạn kết nối qua địa chỉ này. Không có id trong đường dẫn thì kết nối không được thiết lập. - Token kết nối — hiển thị một lần khi tạo. Sao chép và lưu ở nơi an toàn; không thể xem lại token (nó chỉ được lưu dưới dạng hash).
- Cấp lại token — nút tạo token mới và ngay lập tức vô hiệu hóa token cũ. Sau khi cấp lại, hãy cập nhật token trong dịch vụ của bạn.
- Chỉ báo trực tuyến — hiển thị liệu dịch vụ của bạn có đang duy trì kết nối hoạt động hay không.
- Địa chỉ WebSocket — dạng
Các tham số tích hợp bổ sung ảnh hưởng đến giao thức:
session_ttl_seconds— thời gian tồn tại của phiên tính bằng giây (mặc định3600).on_unavailable_reaction_id— phản ứng dự phòng được khởi chạy nếu dịch vụ ngoại tuyến tại thời điểm kích hoạt (xem Độ tin cậy).
Liên kết với phản ứng
Trình xử lý bên ngoài không được kết nối bằng trình kích hoạt riêng mà bằng hành động. Trong trình chỉnh sửa phản ứng, thêm hành động Chuyển cho trình xử lý bên ngoài và chọn tích hợp cần thiết.
Hội thoại có thể được mở bởi bất kỳ trình kích hoạt GetMyBot nào hiện có — lệnh, văn bản, nhấn nút, tham số từ liên kết, yêu cầu web đến, lịch trình. Khi trình kích hoạt như vậy được kích hoạt và đến hành động, GetMyBot mở phiên proxy và gửi sự kiện session.open đến dịch vụ.
Nếu lúc này dịch vụ không kết nối, GetMyBot sẽ khởi chạy phản ứng dự phòng từ cài đặt tích hợp (on_unavailable_reaction_id). Nếu không có phản ứng dự phòng — không có gì xảy ra (âm thầm, không có lỗi cho người dùng).
Kết nối và xác thực
Endpoint: GET /ext/ws/{integrationID} qua WebSocket (wss). integrationID — UUID của tích hợp loại "Trình xử lý bên ngoài". Giới hạn kích thước frame — 256 KiB.
Có thể xác thực theo một trong hai cách.
1. Tiêu đề khi bắt tay (khuyến nghị). Truyền token trong tiêu đề Authorization:
Authorization: Bearer <token>
2. Frame xác thực. Nếu tiêu đề Bearer không được truyền, trong vòng 10 giây sau khi thiết lập kết nối, hãy gửi frame đầu tiên:
{ "type": "auth", "token": "<token>" }
Nếu frame xác thực không đến trong 10 giây — kết nối đóng với mã 4401. Token sai trong cách nào cũng đóng 4401.
Token được tạo khi tạo tích hợp, hiển thị một lần và chỉ được lưu dưới dạng hash (sha256), kiểm tra theo thời gian cố định. Xoay vòng token:
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
Endpoint trả về plaintext-token mới; token cũ ngay lập tức ngừng hoạt động.
Sự kiện: bot → dịch vụ
Mỗi sự kiện là một JSON-frame (EventEnvelope). Trường type — một trong: session.open, message, callback, session.cancel, session.expired.
Tập hợp trường đầy đủ của 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 thô (raw JSON)" },
"event": { "...": "sự kiện GetMyBot đã chuẩn hóa (raw JSON)" }
}
session_id— định danh phiên (hội thoại).bot_id— định danh bot.user— dữ liệu người dùng:tg_user_id(int64),first_namevàusernametùy chọn, cũng nhưparams— tham số người dùng (bao gồm từ liên kết), map khóa→giá trị.chat— chat:id(int64) vàtype(ví dụ"private").update— Telegram Update thô (raw JSON). Có mặt chosession.open,message,callback.event— sự kiện GetMyBot đã chuẩn hóa (raw JSON).
Nội dung khác nhau theo loại:
session.open— trình kích hoạt được kích hoạt, hội thoại mới được mở. Cóupdate(Update thô) vàevent(sự kiện đã chuẩn hóa đầy đủ),user.paramsđược điền.message— đến khi dịch vụ "đang chờ" (expect=text/any) và người dùng gửi tin nhắn. Nội dung giống nhưsession.open.callback— người dùng nhấn nút mà dịch vụ đã gửi trước đó.updatethô; tronguserchỉ cótg_user_id;eventchỉ chứa callback_data gốc do dịch vụ đặt trong nút:
{ "callback_data": "<giá trị gốc do dịch vụ đặt trong nút>" }
session.cancel/session.expired— envelope tối thiểu:session_id,bot_id,user.tg_user_id,chat.id(vớicancelcòn cóchat.type). Không có trườngupdate/event. Các sự kiện này chỉ được giao khi dịch vụ trực tuyến — chúng không được đưa vào hàng đợi ngoại tuyến.
Lệnh: dịch vụ → bot
Lệnh là một JSON-frame (CommandEnvelope). Trường type — một trong: execute, session.close, auth, ping, pong.
{
"type": "execute",
"session_id": "string",
"methods": [
{ "method": "sendMessage", "params": { "text": "Xin chào" } }
],
"expect": "none"
}
session_id— phiên mà lệnh liên quan đến.token— chỉ chotype: "auth"(xem Kết nối và xác thực).methods— mảng các đối tượng{ "method": "<tên phương thức Telegram Bot API>", "params": { ... } }. Tên phương thức — giống hệt như trong Telegram Bot API (ví dụsendMessage),params— đối tượng tham số của phương thức đó.expect— liệu dịch vụ có chờ phản hồi của người dùng không:"none","text"hoặc"any"(giá trị trống được coi lànone).session.close— đóng hội thoại một cách rõ ràng.
Các lệnh không đi thẳng đến Telegram: GetMyBot xác thực chúng và đưa vào outbox của mình, từ đó gửi bằng token bot (với rate-limit, thử lại, loại trùng lặp, nhật ký).
Cách xử lý execute
Lệnh execute bị bỏ qua hoàn toàn nếu: session_id trống; phiên không tìm thấy hoặc không ở trạng thái open; phiên thuộc về tích hợp khác; phiên đã hết hạn.
Tiếp theo:
- Rate-limit: tối đa 60
executemỗi phút cho mỗi phiên (cửa sổ trượt). Vượt giới hạn — bị bỏ. - Tối đa 30 phương thức trong một
execute; phần dư bị bỏ lặng lẽ. - Mỗi phương thức được kiểm tra whitelist (xem Các phương thức được phép) — phương thức không trong danh sách bị từ chối.
- Chat-guard: nếu trong
paramscóchat_idhoặcfrom_chat_idvới giá trị không bằng0và không bằngchat_idcủa phiên — toàn bộ phương thức bị từ chối (không được gửi đến chat người khác).chat_idcó thể bỏ qua hoàn toàn: GetMyBot sẽ tự động điền chat của phiên. - Nút inline với
callback_datađược tự động token hóa (dạng nội bộx:<token>); thời gian tồn tại của nút = đến khi phiên hết hạn. Các núturl/webapp/switch_inlinekhông bị thay đổi.
Các phương thức được phép
Dịch vụ ra lệnh cho bot, vì vậy bộ phương thức bị giới hạn theo whitelist — chỉ gửi và làm việc với tin nhắn của hội thoại hiện tại. Các phương thức không trong danh sách bị bỏ qua lặng lẽ (đây không phải lỗi).
- gửi:
sendMessage,sendPhoto,sendDocument,sendVideo,sendAudio,sendMediaGroup,sendAnimation,sendVoice,sendLocation,sendChatAction; - sửa và xóa:
editMessageText,editMessageCaption,editMessageReplyMarkup,deleteMessage; - trả lời nhấn nút:
answerCallbackQuery; - chuyển tiếp và sao chép:
forwardMessage,copyMessage; - ghim:
pinChatMessage,unpinChatMessage.
Các phương thức cấp tài khoản (setWebhook, getUpdates, logOut, close, setMyCommands, v.v.) không được phép.
Chờ và vòng đời phiên
Sau mỗi execute, số phận của phiên phụ thuộc vào expect và liệu lệnh có chứa nút inline với callback_data hay không:
expect=texthoặcany— tin nhắn tiếp theo của người dùng sẽ được chuyển đến dịch vụ dưới dạng sự kiệnmessage.expect=noneVÀ lệnh KHÔNG có nút inline với callback — phiên tự động đóng. Đây là tin nhắn "cuối cùng".expect=none, NHƯNG có nút callback — phiên vẫn mở: các lần nhấn được bắt trong khi token nút còn sống (đến TTL).
Quan trọng: expect không ảnh hưởng đến việc nhận lần nhấn nút. Các lần nhấn nút đã token hóa được bắt độc lập với giá trị expect — trong khi phiên còn sống và token nút còn hiệu lực. expect chỉ kiểm soát liệu dịch vụ có chờ phản hồi văn bản/tùy ý hay không.
Ranh giới hội thoại
Ngữ cảnh hội thoại tồn tại chính xác cho đến khi phiên còn mở:
- Phản hồi văn bản khi đang chờ tích cực (
expect=text/any) — được gửi đến dịch vụ dưới dạng sự kiệnmessage, không khởi chạy các phản ứng bot thông thường. - Nhấn nút đã token hóa — được gửi đến dịch vụ dưới dạng sự kiện
callbackvớicallback_datagốc. GetMyBot luôn tự xác nhận lần nhấn (answerCallbackQuery); tư cách của lần nhấn được kiểm tra (bot + người dùng + chat của phiên). Nút không đóng phiên. /cancel(cũng như/cancel@botvà "hủy") khi có phiên bên ngoài đang hoạt động — phiên đóng,session.cancelđược gửi đến dịch vụ, người dùng nhận được "Đã hủy.".- Một hội thoại cho mỗi người dùng. Mở phiên mới sẽ thay thế phiên đang mở trước đó của người dùng —
session.cancelđược gửi cho phiên đó. - Hết TTL. Theo bộ hẹn giờ, phiên được đánh dấu là
expiredvà (nếu dịch vụ trực tuyến)session.expiredđược gửi đến dịch vụ.
Giới hạn và thời gian chờ
- Kích thước frame: 256 KiB.
- TTL phiên: mặc định 3600 giây (có thể cấu hình bằng tham số
session_ttl_secondscủa tích hợp). - Tần suất lệnh: 60
executemỗi phút cho mỗi phiên (cửa sổ trượt). - Phương thức trong một
execute: tối đa 30 (phần dư bị bỏ). - Phiên mở cho mỗi tích hợp: tối đa 1000.
- Hàng đợi ngoại tuyến: tối đa 100 sự kiện cho mỗi phiên.
- Heartbeat:
pingmỗi 30 giây; thời gian chờ không hoạt động — 60 giây. - Thời gian chờ frame xác thực: 10 giây.
Độ tin cậy (heartbeat, ngoại tuyến, hàng đợi, fallback)
- Heartbeat. Server gửi frame
{"type":"ping"}mỗi 30 giây. Nếu không có hoạt động nào từ client trong hơn 60 giây — kết nối đóng. Client có thể gửiping/pongcủa riêng mình để duy trì hoạt động. Đứt kết nối không hủy hội thoại: các phiên tồn tại trong DB đến TTL hoặc kết nối lại. - Kết nối lại — phía dịch vụ. Nếu kết nối bị ngắt, dịch vụ của bạn phải kết nối lại. Các phiên đang mở trong DB sẽ tồn tại qua các lần khởi động lại cho đến khi TTL hết hạn.
- Fallback khi ngoại tuyến lúc khởi động. Nếu tại thời điểm kích hoạt phản ứng dịch vụ không kết nối — phản ứng dự phòng từ cài đặt tích hợp (
on_unavailable_reaction_id) sẽ khởi chạy. Nếu nó không được đặt — không có gì xảy ra (âm thầm). - Hàng đợi ngoại tuyến. Các sự kiện
message/callbackkhông giao được do ngoại tuyến được lưu vào hàng đợi ngoại tuyến (tối đa 100 sự kiện, FIFO, được lưu cho đến khi phiên hết hạn) và được gửi khi kết nối lại. Các sự kiệnsession.open/session.cancel/session.expiredkhông được đưa vào hàng đợi. - Đảm bảo giao hàng — at-most-once. Khi hàng đợi tràn hoặc hết hạn, các sự kiện bị bỏ. Hãy thiết kế logic sao cho việc bỏ lỡ một sự kiện riêng lẻ không làm hỏng kịch bản.
Chỉ báo trực tuyến
Có thể kiểm tra liệu dịch vụ có đang duy trì kết nối hoạt động không qua endpoint:
GET /api/bots/{botID}/integrations/{integrationID}/status
Phản hồi:
{ "online": true }
Chỉ báo tương tự có sẵn trên thẻ tích hợp trong bảng điều khiển. Tương đương trong MCP — công cụ get_integration_status.
Bảo mật
GetMyBot thực thi các lệnh của dịch vụ bên ngoài bằng token của mình, vì vậy việc bảo vệ rất nghiêm ngặt.
- Không có bề mặt SSRF. GetMyBot đóng vai trò là WebSocket-server và bản thân không thực hiện kết nối ra ngoài đến dịch vụ — chính dịch vụ kết nối đến GetMyBot. Không có URL do bên ngoài kiểm soát mà nền tảng truy cập.
- Token được lưu dưới dạng hash (sha256), kiểm tra theo thời gian cố định, hỗ trợ xoay vòng.
- Cô lập thuê bao. Lệnh bị ràng buộc chặt chẽ với tích hợp của mình; phiên — với bot + người dùng + chat. Các sự kiện của bot người khác không đi vào socket của bạn.
- Whitelist phương thức +
chat_idbắt buộc ngăn bot trở thành máy phát trong các chat tùy ý. - Token bot không bị lộ ra ngoài — dịch vụ không bao giờ giao tiếp trực tiếp với Telegram.
- Ẩn bí mật. Token tích hợp và các giá trị nhạy cảm bị ẩn trong nhật ký và log.
Ví dụ
Trình xử lý tối giản: khi mở hội thoại, gửi tin nhắn và đóng phiên (expect: "none"). Thay thế id tích hợp và token. Địa chỉ phải chứa <integrationID> trong đường dẫn.
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: "Xin chào từ trình xử lý bên ngoài!" } }],
}));
}
});
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": "Xin chào từ trình xử lý bên ngoài!"}}
],
}))
asyncio.run(main())
Nút inline và xử lý callback
Để bắt lần nhấn, hãy gửi nút với callback_data (GetMyBot tự động token hóa nó). Với expect: "none" có nút, phiên vẫn mở trong khi token nút còn sống — lần nhấn sẽ đến dưới dạng sự kiện callback, trong đó event.callback_data bằng giá trị gốc. Không cần xác nhận lần nhấn (answerCallbackQuery) — GetMyBot tự làm điều đó.
if (ev.type === "session.open") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{
method: "sendMessage",
params: {
text: "Nhấn nút",
reply_markup: { inline_keyboard: [[{ text: "Bắt đầu", 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: "Đã nhấn nút!" } }],
}));
}
Tiếp theo
- Tích hợp — tổng quan về các dịch vụ có thể kết nối.
- Phản ứng — cách xây dựng trình kích hoạt, điều kiện và hành động.
- Yêu cầu web và webhook — cách đơn giản hơn để gọi URL bên ngoài mà không cần hội thoại.