Skip to main content

Room API

WebSocket-based state synchronisation (v2 protocol). Rooms use in-memory state for fast reads/writes with periodic Durable Object Storage persistence for hibernation recovery. When all players disconnect, the room hibernates to zero cost and restores state on reconnect. Suitable for game lobbies, collaborative editors, and live dashboards.


Connection

wss://your-project.edgebase.dev/api/room?namespace=game&id=lobby-1
ParameterTypeRequiredDescription
namespacestringYesRoom namespace (e.g. game, chat)
idstringYesRoom instance ID within the namespace

Authentication

Send an auth message as the first WebSocket message:

{ "type": "auth", "token": "<accessToken>" }

On success:

{ "type": "auth_success", "userId": "user_123", "connectionId": "conn_abc" }

On re-authentication (token refresh during connection):

{ "type": "auth_refreshed", "userId": "user_123", "connectionId": "conn_abc" }

On failure:

{ "type": "error", "code": "AUTH_FAILED", "message": "Invalid or expired token" }

Authentication must be completed within 5 seconds (default) or the connection is closed with AUTH_TIMEOUT.


Client to Server Messages

auth

Authenticate the connection.

{ "type": "auth", "token": "<accessToken>" }

join

Join the room after authentication. Supports state recovery via last known versions.

{ "type": "join" }

With state recovery (reconnect/eviction recovery):

{
"type": "join",
"lastSharedState": { "score": 10 },
"lastSharedVersion": 5,
"lastPlayerState": { "inventory": [] },
"lastPlayerVersion": 3
}
FieldTypeRequiredDescription
lastSharedStateobjectNoLast known shared state for recovery
lastSharedVersionnumberNoLast known shared state version
lastPlayerStateobjectNoLast known player state for recovery
lastPlayerVersionnumberNoLast known player state version

send

Send an action to the server-side onAction handler. The server responds with action_result or action_error matched by requestId.

{
"type": "send",
"actionType": "MOVE",
"payload": { "x": 10, "y": 20 },
"requestId": "req-abc123"
}
FieldTypeRequiredDescription
actionTypestringYesThe action type to execute
payloadanyNoAction payload (defaults to {})
requestIdstringYesUnique ID for matching the response

ping

Keep-alive heartbeat. Server responds with pong.

{ "type": "ping" }

Server to Client Messages

auth_success

First successful authentication.

{ "type": "auth_success", "userId": "user_123", "connectionId": "conn_abc" }

auth_refreshed

Successful re-authentication (token refresh).

{ "type": "auth_refreshed", "userId": "user_123", "connectionId": "conn_abc" }

sync

Full state snapshot. Sent on join or after hibernation recovery. Contains both shared and player state.

{
"type": "sync",
"sharedState": { "round": 1, "phase": "lobby" },
"sharedVersion": 1,
"playerState": { "inventory": ["sword"] },
"playerVersion": 1
}
FieldTypeDescription
sharedStateobjectComplete shared state visible to all players
sharedVersionnumberShared state version
playerStateobjectThis player's private state
playerVersionnumberPlayer state version

shared_delta

Incremental shared state change using dot-path keys.

{
"type": "shared_delta",
"delta": { "player.position.x": 10 },
"version": 2
}
FieldTypeDescription
deltaobjectChanged dot-path keys and their new values (null = delete)
versionnumberNew shared state version

player_delta

Incremental player state change using dot-path keys.

{
"type": "player_delta",
"delta": { "inventory.0": "shield" },
"version": 2
}
FieldTypeDescription
deltaobjectChanged dot-path keys and their new values (null = delete)
versionnumberNew player state version

action_result

Successful action execution result. Sent only to the client that sent the action, matched by requestId.

{
"type": "action_result",
"requestId": "req-abc123",
"result": { "newPosition": { "x": 5, "y": 3 } }
}

action_error

Action execution failed. Sent only to the client that sent the action, matched by requestId.

{
"type": "action_error",
"requestId": "req-abc123",
"message": "Invalid move"
}

message

Server-sent message (sent by room.sendMessage() or room.sendMessageTo() in server-side code).

{
"type": "message",
"messageType": "game_over",
"data": { "winner": "user_1" }
}

kicked

This client was kicked from the room by server-side code.

{ "type": "kicked" }

error

Error notification.

{ "type": "error", "code": "RATE_LIMITED", "message": "Too many messages" }

pong

Response to ping.


Error Codes

CodeDescription
AUTH_FAILEDAuthentication failed or token invalid
AUTH_REFRESH_FAILEDRe-authentication failed; existing auth preserved
AUTH_TIMEOUTAuth message not received within timeout
INVALID_JSONMessage not valid JSON (closes connection with 4000)
NOT_AUTHENTICATEDOperation requires auth (closes connection with 4000)
RATE_LIMITEDToo many messages (default: 10 msg/s)
INVALID_ACTIONsend message missing actionType
NO_HANDLERNo onAction handler registered for this action type
UNAUTHENTICATEDAction requires authenticated userId
JOIN_DENIEDJoin rule evaluation failed
ROOM_FULLRoom at maxPlayers capacity (HTTP 403)

Limits

LimitDefault
Max players per room100
Max state size1 MB (configurable up to 10 MB)
Max message rate10 msg/s per connection
Max dot-path depth5 levels
onAction timeout5 seconds
Auth timeout5 seconds
Send timeout (client)10 seconds
Pending connections per IP5
Delta batch interval50ms
Hibernation idle timeout300 seconds
State save interval60 seconds
State TTL24 hours

Metadata HTTP Endpoint

GET /api/room/metadata?namespace={ns}&id={roomId}

Retrieve room metadata without joining the room or establishing a WebSocket connection. Metadata is set server-side via room.setMetadata() and persisted to Durable Object storage.

Query ParameterTypeRequiredDescription
namespacestringYesRoom namespace (e.g. game)
idstringYesRoom instance ID within the namespace
curl "https://your-project.edgebase.dev/api/room/metadata?namespace=game&id=lobby-1"

Response 200

{
"mode": "classic",
"playerCount": 5
}

Returns an empty object {} if no metadata has been set for the room.

No authentication required

The metadata endpoint does not require authentication. Developers control what data is exposed by choosing what to pass to room.setMetadata() in their server-side hooks. Only include publicly safe information — never store tokens, emails, or other sensitive data in metadata.

This endpoint is useful for lobby screens, matchmaking UIs, or any scenario where you need to query room information without opening a WebSocket connection.