Reference
Gateway
Connecting to the gateway, HELLO / IDENTIFY / READY, heartbeats and zombie detection, RESUME, Invalid Session, close codes, and the events each intent delivers.
The gateway is a WebSocket at wss://api.voidcom.app/ws (also returned by GET /gateway and GET /gateway/bot; shards is always 1). Frames are JSON text — no zlib-stream, no ETF; query parameters like ?v=10&encoding=json are accepted and ignored. Opcodes and payload shapes follow Discord's.
Connection flow
- Open the socket. The gateway sends HELLO (op 10) immediately:
{ "op": 10, "d": { "heartbeat_interval": 41250 } } - Within 30 seconds send IDENTIFY (op 2) — or RESUME — otherwise the socket is closed:
{ "op": 2, "d": { "token": "YOUR_VOIDCOM_BOT_TOKEN", "intents": 33281 } }shard,large_thresholdandpresenceare accepted and ignored.intentsmust stay within the token's grant — the intent toggles on the application page, baked into the token when it is minted; asking for more closes the socket with 4014Disallowed intent(s)before any READY. - On success the gateway dispatches READY (op 0,
t: "READY",s: 1):{ "v": 10, "user": { "id": "…", "username": "mybot", "bot": true }, "guilds": [{ "id": "…", "unavailable": true }], "session_id": "…", "resume_gateway_url": "wss://api.voidcom.app/ws", "application": { "id": "…", "flags": 0 } }guildslists every server the bot is a member of as unavailable; with theGUILDSintent oneGUILD_CREATEper listed guild follows (channels, roles and the bot's member, fetched as the bot), as on Discord — they are numbered dispatches, so a RESUME replays them. WithoutGUILDSnothing follows: fetchGET /users/@me/guildswhen you need the objects.
Then events flow as op 0 frames with an increasing s.
Heartbeats
Send op 1 every heartbeat_interval ms (41 250) with your last received s as d; the gateway answers op 11 (HEARTBEAT ACK) and refreshes the session's TTL.
If no heartbeat arrives for 2.5 × the interval (≈ 103 s, counted from HELLO and re-armed by each heartbeat), the socket is a zombie: the gateway closes it with code 4000 and the bot should RESUME. It is deliberately not 4009, which would make libraries re-IDENTIFY and lose the replay.
RESUME
After a drop, reconnect to resume_gateway_url, wait for HELLO and send op 6:
{ "op": 6, "d": { "token": "YOUR_VOIDCOM_BOT_TOKEN", "session_id": "…", "seq": 1337 } } Whichever replica you land on loads the session, checks the token belongs to the same bot, replays every recorded frame with s greater than your seq in order, then dispatches RESUMED with s continuing from where the session left off.
Bounds:
| Replay ring | newest 1000 frames per session |
| Session lifetime | 5 minutes after the last heartbeat or dispatched frame — a live, heartbeating session never expires |
What RESUME replays: frames the gateway already produced that the dead socket did not deliver — the common case. What it cannot replay: events that happened while the bot had no session at all (between the session expiring and the next IDENTIFY). Bots that need exactly-once should refetch state on RESUMED the way they do on READY.
Invalid Session
Every failure is the same: op 9 with d: false, then the socket closes. Re-IDENTIFY with a fresh session (there is no d: true resumable variant). Causes:
- the first frame was not a valid IDENTIFY or RESUME payload;
- the token is invalid, revoked or rotated — a bad token is not reported as close 4004; if READY never arrives, check the token;
- RESUME for an unknown or expired session, or a session that belongs to a different bot;
- RESUME for a session whose intents the current token no longer grants (the token was rotated after intents were removed) — re-IDENTIFY with the smaller set;
- the session store is unavailable.
A RESUME whose seq is older than the ring still succeeds: nothing is replayed, then RESUMED.
Close codes
The gateway sends 4000 for a heartbeat timeout, 4014 Disallowed intent(s) when IDENTIFY asks for intents beyond the token's grant (fatal for libraries, as on Discord — enable the intent on the application page and rotate the token, or drop it from IDENTIFY), and a plain close (no 4xxx code) after Invalid Session or when the first frame did not arrive in time. Discord's other 4xxx codes (4004 authentication failed, 4008 rate limited, 4013 invalid intents, …) are not used.
Opcodes
| Op | Name | Direction | On Voidcom |
|---|---|---|---|
| 0 | Dispatch | receive | events, see below |
| 1 | Heartbeat | send | answered with 11 |
| 2 | Identify | send | starts a session |
| 3 | Presence Update | send | accepted, ignored — a bot's presence cannot be set |
| 4 | Voice State Update | send | accepted, ignored — no voice for bots |
| 6 | Resume | send | resumes a session |
| 7 | Reconnect | receive | never sent |
| 8 | Request Guild Members | send | accepted, ignored — use GET /guilds/{id}/members |
| 9 | Invalid Session | receive | always d: false |
| 10 | Hello | receive | first frame |
| 11 | Heartbeat ACK | receive |
Events
Every event the gateway dispatches, the intent that gates it, and where it comes from. INTERACTION_CREATE is delivered regardless of intents. GUILD_MEMBER_* need the privileged GUILD_MEMBERS intent. Message content / embeds / components / attachments need MESSAGE_CONTENT on server messages.
| Event | Intent | Source |
|---|---|---|
READY | — | IDENTIFY (guilds unavailable list from ListUserServers) |
RESUMED | — | RESUME |
MESSAGE_CREATE / MESSAGE_UPDATE / MESSAGE_DELETE | GUILD_MESSAGES or DIRECT_MESSAGES (content / embeds / components / attachments need MESSAGE_CONTENT) | UserEvent.message |
MESSAGE_REACTION_ADD / MESSAGE_REACTION_REMOVE | GUILD_MESSAGE_REACTIONS or DIRECT_MESSAGE_REACTIONS | UserEvent.reaction |
TYPING_START | GUILD_MESSAGE_TYPING or DIRECT_MESSAGE_TYPING | UserEvent.typing |
PRESENCE_UPDATE | GUILD_PRESENCES | UserEvent.presence |
CHANNEL_CREATE / CHANNEL_UPDATE / CHANNEL_DELETE | GUILDS | UserEvent.channel |
GUILD_CREATE | GUILDS | One per guild listed in READY (fetched as the bot: channels, roles, the bot's member; numbered and replayable like every dispatch), ServerEvent CREATED with the server, or the bot's own MEMBER_JOINED (AddBotToServer) |
GUILD_UPDATE | GUILDS | ServerEvent UPDATED with the server (the legacy role-refresh UPDATED without a server is dropped) |
GUILD_DELETE | GUILDS | ServerEvent DELETED, or the bot's own MEMBER_LEFT (RemoveBotFromServer / kick / ban) |
GUILD_MEMBER_ADD / GUILD_MEMBER_UPDATE / GUILD_MEMBER_REMOVE | GUILD_MEMBERS (privileged) | ServerEvent MEMBER_JOINED / MEMBER_UPDATED / MEMBER_LEFT for other members |
GUILD_ROLE_CREATE / GUILD_ROLE_UPDATE / GUILD_ROLE_DELETE | GUILDS | ServerEvent ROLE_CREATED / ROLE_UPDATED / ROLE_DELETED (server ≥ B43a) |
INTERACTION_CREATE | — (always) | UserEvent.interaction |
Not dispatched: friend, mention, emoji, call, key-rotation, notification-preference, server-organisation and voice-state events have no Discord equivalent or are not mapped; there is no VOICE_STATE_UPDATE, GUILD_BAN_ADD/REMOVE, INVITE_CREATE/DELETE, CHANNEL_PINS_UPDATE, MESSAGE_DELETE_BULK, MESSAGE_REACTION_REMOVE_ALL, thread, scheduled-event, stage, auto-moderation or poll event. The full machine-readable list is on Differences from Discord.
Intents
Bit values are Discord's. IDENTIFY with intents beyond the token's grant closes with 4014; granted intents without a row in the events table are accepted and deliver nothing.
| Intent | Bit | Delivers |
|---|---|---|
GUILDS | 1 << 0 | GUILD_*, GUILD_ROLE_*, CHANNEL_* |
GUILD_MEMBERS | 1 << 1 | GUILD_MEMBER_* — privileged |
GUILD_PRESENCES | 1 << 8 | PRESENCE_UPDATE — privileged |
GUILD_MESSAGES | 1 << 9 | MESSAGE_* in servers |
GUILD_MESSAGE_REACTIONS | 1 << 10 | MESSAGE_REACTION_* in servers |
GUILD_MESSAGE_TYPING | 1 << 11 | TYPING_START in servers |
DIRECT_MESSAGES | 1 << 12 | MESSAGE_* in DMs (envelope only — DM content is end-to-end encrypted) |
DIRECT_MESSAGE_REACTIONS | 1 << 13 | MESSAGE_REACTION_* in DMs |
DIRECT_MESSAGE_TYPING | 1 << 14 | TYPING_START in DMs |
MESSAGE_CONTENT | 1 << 15 | unlocks content, embeds, components, attachments on server messages — privileged |
GUILD_MODERATION, GUILD_EXPRESSIONS, GUILD_INTEGRATIONS, GUILD_WEBHOOKS, GUILD_INVITES, GUILD_VOICE_STATES, GUILD_SCHEDULED_EVENTS, AUTO_MODERATION_*, *_POLLS | as on Discord | nothing in v1 |