Buko Docs

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:

FieldMeaning
messageNew message visible to the bot.
edited_messageHuman message edit visible to the bot.
my_chat_memberBot 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:

FieldDescription
offsetNext update id to return. Passing last_update_id + 1 confirms older updates.
limitMaximum updates to return. Maximum 100.
timeoutLong-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"
  }
}