Receiving Updates
Bots receive updates through Bot Gateway WebSocket or polling.
Delivery is at least once. Your agent must deduplicate by update_id.
Update shape
{
"update_id": "10001",
"message": {
"message_id": "42",
"date": 1783000000,
"chat": {
"id": "spc_abc123",
"type": "private"
},
"from": {
"id": "bot_scoped_user_abc",
"is_bot": false,
"display_name": "Alice",
"handle": "alice"
},
"text": "/start",
"entities": [
{ "type": "bot_command", "offset": 0, "length": 6 }
]
}
}
Supported update fields:
| Field | Meaning |
|---|---|
message | New message visible to the bot. |
edited_message | Human message edit visible to the bot. |
my_chat_member | Bot relationship or membership change. |
Bot Gateway WebSocket
GET wss://ims.buko.app/bot/ws
Use the normal Bot authorization header during the upgrade.
Incoming frame:
{
"type": "update",
"update": {
"update_id": "10001",
"message": {
"message_id": "42",
"chat": { "id": "spc_abc123", "type": "private" },
"text": "hello"
}
}
}
Ack frame:
{
"type": "ack",
"update_id": "10001"
}
Ack is cumulative: acknowledging 10001 confirms all updates up to and including 10001.
Ping/pong frames are allowed:
{ "type": "ping" }
{ "type": "pong" }
JavaScript example
import WebSocket from "ws";
const token = process.env.BUKO_BOT_TOKEN;
const ws = new WebSocket("wss://ims.buko.app/bot/ws", {
headers: { Authorization: `Bot ${token}` },
});
ws.on("message", async (raw) => {
const frame = JSON.parse(raw.toString());
if (frame.type !== "update") return;
const update = frame.update;
console.log("update", update.update_id, update.message?.text);
ws.send(JSON.stringify({ type: "ack", update_id: update.update_id }));
});
Polling fallback
POST https://ims.buko.app/bot/getUpdates
Polling is useful for simple deployments or reverse proxies that do not support WebSocket. If the bot already has an active Gateway connection, polling returns 409 GATEWAY_ACTIVE.
Request:
{
"offset": "10002",
"limit": 50,
"timeout": 25
}
Fields:
| Field | Description |
|---|---|
offset | Next update id to return. Passing last_update_id + 1 confirms older updates. |
limit | Maximum updates to return. Maximum 100. |
timeout | Long-poll timeout in seconds. Maximum 25. |
Example:
curl -sS https://ims.buko.app/bot/getUpdates \
-X POST \
-H "Authorization: Bot $BUKO_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"offset":"0","limit":50,"timeout":20}'
Response:
{
"ok": true,
"result": [
{
"update_id": "10001",
"message": {
"message_id": "42",
"chat": { "id": "spc_abc123", "type": "private" },
"text": "hello"
}
}
]
}
Lifecycle updates
When a user stops or blocks the bot, or when a bot is removed from a group, the bot receives my_chat_member when the event is visible to the bot. Private stop/block uses started -> stopped; group removal or group dissolution uses member -> removed.
{
"update_id": "10002",
"my_chat_member": {
"chat": { "id": "spc_abc123", "type": "private" },
"from": {
"id": "bot_scoped_user_abc",
"is_bot": false,
"display_name": "Alice"
},
"date": 1783000000,
"old_status": "started",
"new_status": "stopped"
}
}
For MVP integrations, treat the visible /start message as the primary start signal. my_chat_member is used for stop, block, removal, and group lifecycle events.
Edited messages
{
"update_id": "10003",
"edited_message": {
"message_id": "45",
"edit_date": 1783000300,
"chat": { "id": "spc_abc123", "type": "private" },
"from": {
"id": "bot_scoped_user_abc",
"is_bot": false,
"display_name": "Alice"
},
"text": "edited text"
}
}