Skip to content

Developer docs

The bot gateway

Hear what happens in your hubs as it happens over a WebSocket: connecting, identifying, heartbeats, resume, intents, events, shards and limits.

The gateway is a WebSocket that delivers events to your bot as they happen. A bot needs the gateway scope, in the application and in each hub, plus the scope each event needs. What a bot hears is never more than what its REST calls could read.

Connect

  1. GET /api/developer/v1/gateway/bot returns the url to connect to, the recommended shards, maxShards, the heartbeatInterval and what each intent needs.
  2. Open a WebSocket to url over HTTP/1.1, with Authorization: Bot <token>. Clients that cannot set headers (browsers, the built-in WebSocket in Node) can connect without it and put token in IDENTIFY instead.
  3. The gateway sends HELLO: { "op": 10, "d": { "heartbeatInterval": 41250 } }.
  4. Send IDENTIFY within 10 seconds: { "op": 2, "d": { "intents": ["HUBS", "HUB_MESSAGES"], "shard": [0, 1] } }. Intents may also be a number.
  5. The gateway answers with the READY event, listing the hubs on this shard that granted live events.
A small bot
const token = process.env.HUBS_BOT_TOKEN;
const auth = { Authorization: `Bot ${token}` };
const info = await (
  await fetch("https://hubs.example.com/api/developer/v1/gateway/bot", { headers: auth })
).json();

let seq = null;
let sessionId = null;
let beat;
// Browsers and Node's built-in WebSocket cannot set headers, so the token
// goes in IDENTIFY instead.
const socket = new WebSocket(info.url);
socket.onmessage = ({ data }) => {
  const frame = JSON.parse(data);
  if (frame.s) seq = frame.s;
  if (frame.op === 10) {
    beat = setInterval(
      () => socket.send(JSON.stringify({ op: 1, d: seq })),
      frame.d.heartbeatInterval,
    );
    socket.send(
      JSON.stringify({
        op: 2,
        d: { token, intents: ["HUBS", "HUB_MESSAGES"], shard: [0, 1] },
      }),
    );
  } else if (frame.op === 0 && frame.t === "READY") {
    sessionId = frame.d.sessionId;
  } else if (frame.op === 0 && frame.t === "MESSAGE_CREATE") {
    console.log(frame.d.channelId, frame.d.content);
  }
};
socket.onclose = () => clearInterval(beat);

Every event after READY is { "op": 0, "t": "<EVENT_NAME>", "s": <sequence>, "d": <data> }. Sequence numbers go up by one per event in a session.

Opcodes

OpNameDirectionMeaning
0DISPATCHReceiveAn event, with its name in t and a sequence in s
1HEARTBEATSendKeep the session alive; d is the last sequence
2IDENTIFYSendStart a session with intents and a shard
6RESUMESendContinue a session after a reconnect
7RECONNECTReceiveReconnect and resume (the server is restarting)
9INVALID SESSIONReceiveStart over with IDENTIFY
10HELLOReceiveThe first frame, with the heartbeat interval
11HEARTBEAT ACKReceiveThe answer to a heartbeat

Heartbeats

Send { "op": 1, "d": <last sequence> } at least every heartbeatInterval (41,250 ms); the gateway answers { "op": 11 }. A session with no heartbeat for one and a half intervals is closed with 4009 and can be resumed. Each heartbeat also checks the token, so a reset or revoked token closes the socket at once.

Resume and reconnect

  • After any close that allows it (see the close codes), reconnect and send RESUME within 2 minutes: { "op": 6, "d": { "sessionId": "...", "seq": <last sequence> } }. The gateway replays what you missed (up to 1,000 events) with the sequence numbers you would have seen, then sends RESUMED.
  • Missed events are checked again when they are replayed, so an event the bot lost access to while away is skipped.
  • { "op": 9, "d": false } means the session cannot be resumed: start over with IDENTIFY.
  • { "op": 7 } comes before a server restart: reconnect and resume.
  • Interactions are not replayed after their answer window.

Intents

Intents choose which events a session hears. Each one needs a scope in the application and in the hub, and that scope's permission on the bot's role. Every bot also hears HUB_CREATE, HUB_UPDATE and HUB_DELETE for its own installs, and INTERACTION_CREATE.

IntentBitEventsNeeds
HUBS1CHANNEL_CREATE, CHANNEL_UPDATE, CHANNEL_DELETEchannels:read
HUB_MEMBERS2HUB_MEMBER_ADD, HUB_MEMBER_REMOVEmembers:read
HUB_MESSAGES512MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETEmessages:read
HUB_MESSAGE_REACTIONS1024MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVEmessages:read
MESSAGE_CONTENT32768Message text, files and embeds in message eventsmessages:read

Without MESSAGE_CONTENT, message events arrive with empty content, attachments, embeds and components, except for the bot's own messages and messages that mention its handle.

Events

EventWhenData
READYAfter IDENTIFYv, sessionId, user, application, shard, intents, hubs
RESUMEDAfter a replayEmpty
HUB_CREATE, HUB_UPDATEThe bot is added or its grants changeThe hub with its scopes and permissions
HUB_DELETEThe bot is removedid only
CHANNEL_CREATE, CHANNEL_UPDATEA chat or announcement channel the bot can openThe channel, as the REST API returns it
CHANNEL_DELETEA channel is deletedid and hubId only
HUB_MEMBER_ADD, HUB_MEMBER_REMOVESomeone joins or leaveshubId and user: id, handle, displayName, bot
MESSAGE_CREATE, MESSAGE_UPDATEA message is posted or editedThe message, as the REST API returns it
MESSAGE_DELETEA message is deletedid, channelId, hubId
MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVEA reaction changesmessageId, channelId, hubId, emoji, userId
INTERACTION_CREATESomeone uses a command or controlSee interactions

Who hears what

Each event is checked for each session when it is delivered: the token is current and the application enabled, the bot is still in the hub and not banned, the hub granted gateway and the intent's scope, the bot's role has the permission, and for anything in a channel, the bot's role can open that channel. A staff-only channel, a channel closed to the bot's role, or a hub that did not grant live events stays silent.

Shards

A bot in many hubs can split its events across up to 16 sessions. A hub belongs to shard parseInt(first 8 hex digits of the hub id, 16) % shardCount. Hubs recommends one shard per 1,000 hubs; gateway/bot tells you how many to open.

Close codes

CodeMeaningResume?
4000Unknown error, or the server is restartingYes
4001An opcode the gateway does not knowYes
4002A frame that is not JSONYes
4003No IDENTIFY or RESUME within 10 secondsYes
4004The token is invalid, reset, revoked or disabledNo, start over
4005IDENTIFY sent twiceYes
4007A bad sequence number on RESUMENo, start over
4008More than 120 frames a minute, or too many sessionsYes
4009No heartbeatYes
4010An invalid shardNo, start over
4013Unknown intentsNo, start over
4014An intent the application has no scope forNo, start over

Limits

  • Frames from a bot are text JSON, at most 16 KB each and 120 a minute.
  • Up to 16 open sessions per bot. Opening a connection counts as one API call.
  • Connect over HTTP/1.1. A request that reaches the gateway address without a WebSocket upgrade (for example over HTTP/2) gets 426 with BOT_GATEWAY_UPGRADE_REQUIRED.