Penangan Eksternal

Penangan eksternal adalah jembatan antara kode Anda dan Telegram. Ketika logika dari pembuat reaksi tidak mencukupi, Anda menyerahkan reaksi tertentu ke server Anda sendiri: server menerima peristiwa dan merespons dengan perintah dalam format Telegram Bot API, sementara GetMyBot menangani transport, pengiriman, dan semua hal yang berkaitan dengan Telegram.

Apa itu

Reaksi di GetMyBot dibuat secara visual: pemicu → kondisi → tindakan. Logika yang kompleks atau non-standar — basis data Anda sendiri, kalkulasi, panggilan ke sistem pihak ketiga, ML, skenario bercabang — sulit untuk diekspresikan di pembuat reaksi. Penangan eksternal menghilangkan batasan ini: Anda menghubungkan backend Anda sendiri dan menyerahkan seluruh reaksi kepadanya untuk diproses.

Layanan eksternal menerima peristiwa dari GetMyBot (pemicu aktif, pengguna membalas teks, menekan tombol) dan sebagai respons memerintahkan bot untuk mengirim sesuatu. GetMyBot mengeksekusi perintah dengan token botnya sendiri — di atas itu bekerja rate-limit, percobaan ulang, deduplikasi, dan log dialog.

Model kerja

  • GetMyBot adalah server WebSocket. Layanan Anda sendiri yang terhubung ke server tersebut dan tidak memerlukan alamat publik atau endpoint webhook. GetMyBot tidak membuat koneksi keluar ke layanan Anda, sehingga tidak ada permukaan SSRF.
  • Autentikasi dengan token integrasi. Layanan menyajikan token saat terhubung; GetMyBot memverifikasi dengan hash dan menjaga koneksi.
  • Peristiwa dan perintah dalam format Telegram Bot API. Layanan menerima peristiwa dan merespons dengan array panggilan berformat "metode + parameter", persis seperti saat bekerja dengan bot biasa.
  • Perintah dieksekusi oleh GetMyBot dengan tokennya sendiri. Layanan tidak pernah berkomunikasi langsung dengan Telegram: perintah divalidasi dan masuk ke outbox internal GetMyBot, dari mana dikirim dengan token bot beserta rate-limit, percobaan ulang, deduplikasi, dan log.

Satu koneksi melayani semua dialog dari satu integrasi — peristiwa dari berbagai pengguna dimultipleks dan dibedakan berdasarkan field session_id.

Mengatur integrasi

  1. Buka bagian Integrasi di menu sebelah kiri dan klik Tambah integrasi.
  2. Pilih layanan Penangan eksternal dan tentukan nama.
  3. Setelah dibuat, kartu akan menampilkan:
    • Alamat WebSocket — berformat wss://api.mybot.app/ext/ws/<integrationID>, di mana <integrationID> adalah UUID integrasi ini. Layanan Anda terhubung melalui alamat ini. Tanpa id dalam jalur, koneksi tidak akan terbentuk.
    • Token koneksi — ditampilkan sekali saja saat pembuatan. Salin dan simpan di tempat yang aman; token tidak dapat dilihat lagi (hanya disimpan dalam bentuk hash).
    • Rotasi token — tombol menghasilkan token baru dan segera membatalkan token lama. Setelah rotasi, perbarui token di layanan Anda.
    • Indikator online — menampilkan apakah layanan Anda saat ini mempertahankan koneksi aktif.

Parameter integrasi tambahan yang mempengaruhi protokol:

  • session_ttl_seconds — waktu hidup sesi dalam detik (default 3600).
  • on_unavailable_reaction_id — reaksi cadangan yang dijalankan jika layanan offline saat pemicu aktif (lihat Keandalan).

Menautkan ke reaksi

Penangan eksternal tidak terhubung sebagai pemicu terpisah, melainkan sebagai tindakan. Di editor reaksi, tambahkan tindakan Serahkan ke penangan eksternal dan pilih integrasi yang diinginkan.

Dialog dapat dibuka oleh pemicu GetMyBot mana pun yang sudah ada — perintah, teks, penekanan tombol, parameter dari tautan, permintaan web masuk, jadwal. Ketika pemicu seperti itu aktif dan mencapai tindakan, GetMyBot membuka sesi proxy dan mengirim peristiwa session.open ke layanan.

Jika layanan tidak terhubung saat itu, GetMyBot akan menjalankan reaksi cadangan dari pengaturan integrasi (on_unavailable_reaction_id). Jika cadangan tidak ditentukan — tidak ada yang terjadi (diam, tanpa error ke pengguna).

Koneksi dan autentikasi

Endpoint: GET /ext/ws/{integrationID} melalui WebSocket (wss). integrationID adalah UUID integrasi bertipe "Penangan eksternal". Batas ukuran frame — 256 KiB.

Ada dua cara untuk melakukan autentikasi.

1. Header saat handshake (direkomendasikan). Kirim token di header Authorization:

Authorization: Bearer <token>

2. Auth-frame. Jika header Bearer tidak dikirim, kirim sebagai frame pertama dalam 10 detik setelah koneksi terbentuk:

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

Jika auth-frame tidak diterima dalam 10 detik — koneksi ditutup dengan kode 4401. Token yang salah dengan cara apa pun juga menghasilkan close 4401.

Token dihasilkan saat pembuatan integrasi, ditampilkan sekali, dan disimpan hanya dalam bentuk hash (sha256), verifikasi menggunakan constant-time. Rotasi token:

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

Endpoint mengembalikan plaintext token baru; token lama segera berhenti berfungsi.

Peristiwa: bot → layanan

Setiap peristiwa adalah JSON-frame (EventEnvelope). Field type — salah satu dari: session.open, message, callback, session.cancel, session.expired.

Set lengkap field 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 mentah (raw JSON)" },
  "event": { "...": "peristiwa GetMyBot yang dinormalisasi (raw JSON)" }
}
  • session_id — pengidentifikasi sesi (dialog).
  • bot_id — pengidentifikasi bot.
  • user — data pengguna: tg_user_id (int64), first_name dan username opsional, serta params — parameter pengguna (termasuk dari tautan), map kunci→nilai.
  • chat — obrolan: id (int64) dan type (misalnya "private").
  • update — Telegram Update mentah (raw JSON). Ada untuk session.open, message, callback.
  • event — peristiwa GetMyBot yang dinormalisasi (raw JSON).

Konten berdasarkan jenis berbeda-beda:

  • session.open — pemicu aktif, dialog baru dibuka. Ada update (Update mentah) dan event (peristiwa dinormalisasi lengkap), user.params terisi.
  • message — dikirim saat layanan "menunggu" (expect = text/any) dan pengguna mengirim pesan. Konten sama seperti session.open.
  • callback — pengguna menekan tombol yang sebelumnya dikirim layanan. update mentah; di user hanya tg_user_id yang terisi; event hanya berisi callback_data asli yang ditentukan layanan di tombol:
{ "callback_data": "<nilai asli yang ditentukan layanan di tombol>" }
  • session.cancel / session.expired — envelope minimal: session_id, bot_id, user.tg_user_id, chat.id (untuk cancel juga chat.type). Field update/event tidak ada. Peristiwa ini hanya dikirimkan jika layanan online — tidak disimpan ke antrean offline.

Perintah: layanan → bot

Perintah adalah JSON-frame (CommandEnvelope). Field type — salah satu dari: execute, session.close, auth, ping, pong.

{
  "type": "execute",
  "session_id": "string",
  "methods": [
    { "method": "sendMessage", "params": { "text": "Halo" } }
  ],
  "expect": "none"
}
  • session_id — sesi yang terkait dengan perintah.
  • token — hanya untuk type: "auth" (lihat Koneksi dan autentikasi).
  • methods — array objek { "method": "<nama metode Telegram Bot API>", "params": { ... } }. Nama metode persis seperti di Telegram Bot API (misalnya sendMessage), params adalah objek parameter metode tersebut.
  • expect — apakah layanan menunggu respons pengguna: "none", "text", atau "any" (nilai kosong diperlakukan sebagai none).
  • session.close — menutup dialog secara eksplisit.

Perintah tidak langsung dikirim ke Telegram: GetMyBot memvalidasi dan memasukkannya ke outbox-nya, dari mana dikirim dengan token bot (beserta rate-limit, percobaan ulang, deduplikasi, dan log).

Cara execute diproses

Perintah execute dibuang seluruhnya jika: session_id kosong; sesi tidak ditemukan atau tidak berstatus open; sesi milik integrasi lain; sesi sudah kedaluwarsa.

Selanjutnya:

  • Rate-limit: hingga 60 execute per menit per sesi (jendela geser). Melampaui batas — dibuang.
  • Hingga 30 metode dalam satu execute; yang berlebih diabaikan secara diam-diam.
  • Setiap metode diperiksa terhadap whitelist (lihat Metode yang diizinkan) — metode yang tidak ada dalam daftar ditolak.
  • Chat-guard: jika params mengandung chat_id atau from_chat_id dengan nilai yang tidak sama dengan 0 dan tidak sama dengan chat_id sesi — seluruh metode ditolak (tidak boleh mengirim ke obrolan orang lain). chat_id dapat tidak ditentukan sama sekali: GetMyBot akan memaksa mengisi obrolan sesi.
  • Tombol inline dengan callback_data secara otomatis ditokenisasi (bentuk internal x:<token>); masa hidup tombol = hingga sesi berakhir. Tombol url/webapp/switch_inline tidak diubah.

Metode yang diizinkan

Layanan mengendalikan bot, sehingga set metode dibatasi oleh whitelist — hanya pengiriman dan operasi pesan untuk dialog saat ini. Metode di luar daftar diabaikan secara diam-diam (bukan error).

  • pengiriman: sendMessage, sendPhoto, sendDocument, sendVideo, sendAudio, sendMediaGroup, sendAnimation, sendVoice, sendLocation, sendChatAction;
  • edit dan hapus: editMessageText, editMessageCaption, editMessageReplyMarkup, deleteMessage;
  • respons penekanan: answerCallbackQuery;
  • teruskan dan salin: forwardMessage, copyMessage;
  • sematkan: pinChatMessage, unpinChatMessage.

Metode tingkat akun (setWebhook, getUpdates, logOut, close, setMyCommands, dll.) tidak diizinkan.

Setelah setiap execute, nasib sesi bergantung pada expect dan apakah perintah mengandung tombol inline dengan callback_data:

  • expect = text atau any — pesan berikutnya dari pengguna akan dikirim ke layanan sebagai peristiwa message.
  • expect = none DAN perintah TIDAK mengandung tombol inline dengan callback — sesi otomatis ditutup. Ini adalah pesan "terminal".
  • expect = none, TETAPI ada tombol callback — sesi tetap terbuka: penekanan ditangkap selama token tombol masih hidup (hingga TTL).

Penting: expect tidak mempengaruhi penerimaan penekanan tombol. Penekanan tombol yang ditokenisasi ditangkap terlepas dari nilai expect — selama sesi dan token tombol masih hidup. expect hanya mengontrol apakah layanan menunggu respons teks/sembarang.

Batas dialog

Konteks dialog hidup tepat selama sesi terbuka:

  • Respons teks saat menunggu aktif (expect = text/any) — dikirim ke layanan sebagai peristiwa message, bukan memicu reaksi bot biasa.
  • Penekanan tombol yang ditokenisasi — dikirim ke layanan sebagai peristiwa callback dengan callback_data asli. GetMyBot selalu mengonfirmasi penekanan sendiri (answerCallbackQuery); kepemilikan penekanan diverifikasi (bot + pengguna + obrolan sesi). Tombol tidak menutup sesi.
  • /cancel (juga /cancel@bot dan "batal") saat sesi eksternal aktif — sesi ditutup, session.cancel dikirim ke layanan, pengguna menerima "Dibatalkan.".
  • Satu dialog per pengguna. Membuka sesi baru menggeser sesi terbuka sebelumnya dari pengguna tersebut — session.cancel dikirimkan ke sesi lama itu.
  • Kedaluwarsa TTL. Berdasarkan timer, sesi ditandai expired dan (jika layanan online) session.expired dikirim ke layanan.

Batas dan timeout

  • Ukuran frame: 256 KiB.
  • TTL sesi: default 3600 detik (dapat dikonfigurasi melalui parameter session_ttl_seconds integrasi).
  • Frekuensi perintah: 60 execute per menit per sesi (jendela geser).
  • Metode dalam satu execute: hingga 30 (yang berlebih dibuang).
  • Sesi terbuka per integrasi: hingga 1000.
  • Antrean offline: hingga 100 peristiwa per sesi.
  • Heartbeat: ping setiap 30 detik; timeout tidak ada aktivitas — 60 detik.
  • Timeout auth-frame: 10 detik.

Keandalan (heartbeat, offline, antrean, fallback)

  • Heartbeat. Server mengirim frame {"type":"ping"} setiap 30 detik. Jika tidak ada aktivitas dari klien lebih dari 60 detik — koneksi ditutup. Klien dapat mengirim ping/pong sendiri untuk menjaga aktivitas. Putusnya koneksi tidak mematikan dialog: sesi hidup di database hingga TTL atau koneksi ulang.
  • Koneksi ulang — di sisi layanan. Jika koneksi terputus, layanan Anda harus terhubung kembali. Sesi terbuka di database bertahan dari restart hingga TTL berakhir.
  • Fallback saat offline pada awal. Jika layanan tidak terhubung saat reaksi aktif — reaksi cadangan dari pengaturan integrasi dijalankan (on_unavailable_reaction_id). Jika tidak ditentukan — tidak ada yang terjadi (diam).
  • Antrean offline. Peristiwa message/callback yang tidak terkirim karena offline disimpan dalam antrean offline (hingga 100 peristiwa, FIFO, disimpan hingga sesi berakhir) dan dikirimkan saat koneksi ulang. Peristiwa session.open/session.cancel/session.expired tidak disimpan dalam antrean.
  • Jaminan pengiriman — at-most-once. Saat antrean penuh atau kedaluwarsa, peristiwa dibuang. Rancang logika Anda sedemikian rupa sehingga melewatkan satu peristiwa tidak merusak skenario.

Indikator online

Untuk memeriksa apakah layanan mempertahankan koneksi aktif, gunakan endpoint:

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

Respons:

{ "online": true }

Indikator yang sama tersedia di kartu integrasi di panel. Padanan di MCP adalah tool get_integration_status.

Keamanan

GetMyBot mengeksekusi perintah layanan pihak ketiga dengan tokennya sendiri, sehingga perlindungan sangat ketat.

  • Tidak ada permukaan SSRF. GetMyBot berperan sebagai server WebSocket dan tidak membuat koneksi keluar ke layanan — layanan yang terhubung ke GetMyBot. Tidak ada URL yang dikontrol dari luar yang dijangkau platform.
  • Token disimpan dalam hash (sha256), verifikasi constant-time, rotasi didukung.
  • Isolasi tenant. Perintah terikat ketat pada integrasinya sendiri; sesi terikat pada bot + pengguna + obrolan. Peristiwa bot lain tidak masuk ke soket Anda.
  • Whitelist metode + chat_id paksa mencegah bot digunakan sebagai pengirim pesan ke obrolan sembarang.
  • Token bot tidak dikirim keluar — layanan tidak pernah berkomunikasi langsung dengan Telegram.
  • Penyamaran rahasia. Token integrasi dan nilai sensitif disembunyikan dalam log.

Contoh

Penangan minimal: saat dialog dibuka, kirim pesan dan tutup sesi (expect: "none"). Ganti dengan id integrasi dan token Anda. Alamat harus mengandung <integrationID> dalam jalur.

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: "Halo dari penangan eksternal!" } }],
    }));
  }
});

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": "Halo dari penangan eksternal!"}}
                    ],
                }))

asyncio.run(main())

Tombol inline dan penanganan callback

Untuk menangkap penekanan, kirim tombol dengan callback_data (GetMyBot menokenisasinya secara otomatis). Dengan expect: "none" dan tombol, sesi tetap terbuka selama token tombol masih hidup — penekanan akan datang sebagai peristiwa callback, di mana event.callback_data sama dengan nilai aslinya. Mengonfirmasi penekanan (answerCallbackQuery) tidak diperlukan — GetMyBot melakukannya sendiri.

if (ev.type === "session.open") {
  ws.send(JSON.stringify({
    type: "execute",
    session_id: ev.session_id,
    expect: "none",
    methods: [{
      method: "sendMessage",
      params: {
        text: "Tekan tombol",
        reply_markup: { inline_keyboard: [[{ text: "Mulai", 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: "Tombol ditekan!" } }],
  }));
}

Langkah selanjutnya

  • Integrasi — ikhtisar umum layanan yang dapat dihubungkan.
  • Reaksi — cara menyusun pemicu, kondisi, dan tindakan.
  • Permintaan web dan webhook — cara lebih sederhana untuk memanggil URL eksternal tanpa dialog.