Gateway events
Every dispatch frame the Gateway sends carries op: 0 plus a t field naming the event. This page lists every event, its payload shape, and what triggers it.
Most payloads carry only the affected entity's id — clients refetch the canonical shape from REST. That keeps the wire small and means there's one place to change a row's shape (the API) when fields evolve.
Lifecycle
READY
First dispatch on a fresh connection. Carries the full user row so the app can hydrate immediately.
{
"op": 0,
"s": 1,
"t": "READY",
"d": {
"user": { "id": "...", "email": "...", "display_name": "...", ... },
"session_id": "31293...",
"heartbeat_interval": 30000
}
}
- Name
user- Type
- User
- Description
Same shape as
GET /v1/users/@me.
- Name
session_id- Type
- string
- Description
The Gateway session id. Persist this — it's required for
RESUME.
- Name
heartbeat_interval- Type
- integer
- Description
Milliseconds between client heartbeats. Echoed from
HELLOfor clients that bypass it.
RESUMED
Sent after a successful RESUME. Payload is empty — its purpose is to mark the end of the replay buffer so the client can return to normal flow.
{ "op": 0, "s": 9999, "t": "RESUMED", "d": {} }
Messages
MESSAGE_CREATE
Dispatched to every subscriber of the channel.
d
{
"channel_id": "3123...",
"message_id": "9182...",
"ts": 1716929213,
"nonce": "9182374ab"
}
nonce echoes the optimistic id the author submitted on POST /messages so the author's session can reconcile the pending row. Recipients with no matching pending row ignore it. The client should fetch GET /channels/:channel_id/messages/:message_id to hydrate the body.
MESSAGE_UPDATE / MESSAGE_DELETE
Minimal — entity id only. Refetch on update; on delete render the placeholder.
d
{ "channel_id": "3123...", "message_id": "9182..." }
MESSAGE_REACTION_ADD / MESSAGE_REACTION_REMOVE
d
{
"channel_id": "3123...",
"message_id": "9182...",
"user_id": "31223...",
"emoji_id": null,
"emoji_name": "🎉"
}
emoji_id is set when the reaction is a custom emoji; null for unicode. emoji_name is always present — the glyph itself for unicode, the shortcode for custom.
Channels
All three are minimal — the channel id only. Clients refetch when they care.
| Event | Payload | Triggered when |
|---|---|---|
CHANNEL_CREATE | { channel_id } | A new channel becomes visible to the recipient. |
CHANNEL_UPDATE | { channel_id } | Name, topic, icon, scene, owner, etc. changed. |
CHANNEL_DELETE | { channel_id } | Channel removed. |
TYPING_START
Real-time typing indicator. Server side has no TYPING_STOP — clients infer stop from a timer (typically 10s of no activity).
d
{ "channel_id": "3123...", "user_id": "31223..." }
Users
USER_UPDATE
Dispatched to every live session for the affected user when their profile changes. Carries only the user id; refetch /v1/users/@me.
d
{ "user_id": "31223..." }
PRESENCE_UPDATE
Status changes are dispatched to friends + channel co-members.
d
{ "user_id": "31223...", "status": 1 }
status follows the PresenceStatus enum: 0 OFFLINE, 1 ONLINE, 2 IDLE, 3 DND, 4 INVISIBLE.
RELATIONSHIP_UPDATE
Friend request sent / accepted, block applied — both sides receive this event so each can refetch their /relationships list.
d
{ "user_id": "31223...", "target_id": "412412..." }
Inventory + equipment
INVENTORY_ADD / INVENTORY_REMOVE
d
{ "instance_id": "9182..." }
Refetch /v1/users/@me/inventory/:instanceId on add; remove the row locally on remove.
EQUIPMENT_UPDATE
d
{ "user_id": "31223...", "slot": 4, "instance_id": "9182..." }
slot is an EquipmentSlot int. instance_id: null means the slot was unequipped — clients should clear that slot in local state.
Spatial
WARP_UPDATE
"This player is now in HUB/HOME X." Dispatched whenever the wormhole authoritative game server reports a join, room switch, or leave.
d
{
"user_id": "31223...",
"channel_id": "3123...",
"session_id": "wmh_9281..."
}
- Name
user_id- Type
- string
- Description
The player whose location changed.
- Name
channel_id- Type
- string | null
- Description
The HUB or HOME they joined.
nullmeans the user is no longer in any spatial channel (logged out, dropped, etc.).
- Name
session_id- Type
- string | null
- Description
The wormhole's per-spawn id. Clients use it to correlate the dispatch with their own join attempt so a stale leave from a previous session can't blow away current state.
Adding new events
New events land as:
- A new
EventNameentry inpackages/common/src/gateway.ts. - A new payload type in the same file.
- A new variant on the
Dispatchdiscriminated union — the exhaustive switch in client gateway handlers won't compile until it's wired up. - A dispatch site somewhere in the API or game server.
- A row in this page.