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
GET /api/developer/v1/gateway/botreturns theurlto connect to, the recommendedshards,maxShards, theheartbeatIntervaland what each intent needs.- Open a WebSocket to
urlover HTTP/1.1, withAuthorization: Bot <token>. Clients that cannot set headers (browsers, the built-in WebSocket in Node) can connect without it and puttokenin IDENTIFY instead. - The gateway sends HELLO:
{ "op": 10, "d": { "heartbeatInterval": 41250 } }. - Send IDENTIFY within 10 seconds:
{ "op": 2, "d": { "intents": ["HUBS", "HUB_MESSAGES"], "shard": [0, 1] } }. Intents may also be a number. - The gateway answers with the READY event, listing the hubs on this shard that granted live events.
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
| Op | Name | Direction | Meaning |
|---|---|---|---|
0 | DISPATCH | Receive | An event, with its name in t and a sequence in s |
1 | HEARTBEAT | Send | Keep the session alive; d is the last sequence |
2 | IDENTIFY | Send | Start a session with intents and a shard |
6 | RESUME | Send | Continue a session after a reconnect |
7 | RECONNECT | Receive | Reconnect and resume (the server is restarting) |
9 | INVALID SESSION | Receive | Start over with IDENTIFY |
10 | HELLO | Receive | The first frame, with the heartbeat interval |
11 | HEARTBEAT ACK | Receive | The 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.
| Intent | Bit | Events | Needs |
|---|---|---|---|
HUBS | 1 | CHANNEL_CREATE, CHANNEL_UPDATE, CHANNEL_DELETE | channels:read |
HUB_MEMBERS | 2 | HUB_MEMBER_ADD, HUB_MEMBER_REMOVE | members:read |
HUB_MESSAGES | 512 | MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE | messages:read |
HUB_MESSAGE_REACTIONS | 1024 | MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE | messages:read |
MESSAGE_CONTENT | 32768 | Message text, files and embeds in message events | messages: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
| Event | When | Data |
|---|---|---|
READY | After IDENTIFY | v, sessionId, user, application, shard, intents, hubs |
RESUMED | After a replay | Empty |
HUB_CREATE, HUB_UPDATE | The bot is added or its grants change | The hub with its scopes and permissions |
HUB_DELETE | The bot is removed | id only |
CHANNEL_CREATE, CHANNEL_UPDATE | A chat or announcement channel the bot can open | The channel, as the REST API returns it |
CHANNEL_DELETE | A channel is deleted | id and hubId only |
HUB_MEMBER_ADD, HUB_MEMBER_REMOVE | Someone joins or leaves | hubId and user: id, handle, displayName, bot |
MESSAGE_CREATE, MESSAGE_UPDATE | A message is posted or edited | The message, as the REST API returns it |
MESSAGE_DELETE | A message is deleted | id, channelId, hubId |
MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE | A reaction changes | messageId, channelId, hubId, emoji, userId |
INTERACTION_CREATE | Someone uses a command or control | See 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
| Code | Meaning | Resume? |
|---|---|---|
4000 | Unknown error, or the server is restarting | Yes |
4001 | An opcode the gateway does not know | Yes |
4002 | A frame that is not JSON | Yes |
4003 | No IDENTIFY or RESUME within 10 seconds | Yes |
4004 | The token is invalid, reset, revoked or disabled | No, start over |
4005 | IDENTIFY sent twice | Yes |
4007 | A bad sequence number on RESUME | No, start over |
4008 | More than 120 frames a minute, or too many sessions | Yes |
4009 | No heartbeat | Yes |
4010 | An invalid shard | No, start over |
4013 | Unknown intents | No, start over |
4014 | An intent the application has no scope for | No, 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.