Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Realtime overview

This page describes the three WebSockets Open Play offers and when to use each. For in-game state (movement, room, scene) you use the plugin realtime socket, and for notifications and chat you use two separate sockets; all of them have different identity and close rules.

Three sockets

SocketPathWhat forWho speaksCredential
Realtime (plugin)/v1/realtime/wsGame state: live input, state snapshot, durable commands and eventsTwo-waySingle-use ticket
Notification/v1/wsPushing a notification to the player liveServer → player onlyPlayer session (cookie or Bearer)
Chat/v1/chat/wsChannel chat (subscribe, send a message)Two-wayPlayer session (cookie or Bearer)

Rule: all three are player sockets. A socket is not opened with a server key; for server-to-server work use the REST endpoints.

Which one should I use

NeedChoice
Publishing the live state of a vehicle, a player, or a room and receiving player input in orderThe realtime socket, protocol
Getting the result of a command durably and replay-safely (reward, delivery)The realtime Command frame or the HTTP command endpoint (calling plugins)
Showing the player a “new notification” instantly/v1/ws; the durable source is always the GET /v1/notifications inbox (notifications)
Channel chat/v1/chat/ws or REST (chat)
Leaderboard, store, inventoryREST; there is no socket for these

Note

The TypeScript SDK manages the realtime socket for you: getting the ticket, authentication, reconnecting, and snapshot recovery are included. You need to write the protocol by hand only for a client without an SDK (for example the Unity native socket). For SDK usage, see the TypeScript SDK page.

Connection requirements (shared)

  1. HTTPS and an allowed origin. Your browser game calls the API from the same origin through a reverse proxy or, if its domain is registered in an environment, directly at the Open Play address (CORS). Sockets open to the same address: wss://<your-game-domain>/<path> or wss://<open-play-address>/<path>; the handshake checks Origin per environment (CORS does not change this). Details: architecture and hosting.
  2. Environment header. A player identity is always bound to an environment and requires the x-crx-env: <environment uuid> header. On the realtime socket the header is given with the HTTP ticket request; the WebSocket upgrade carries no header, the ticket knows the environment. /v1/ws and /v1/chat/ws, however, require the header on the upgrade request itself. The browser’s WebSocket API cannot add custom headers; therefore these two sockets cannot be opened directly from the browser (a known limitation). In browser games, poll the inbox with REST for notifications and use the REST endpoints for chat; non-browser clients can send the header themselves.
  3. No token in the query string. No socket accepts a session token in the URL; ?token= is not supported so that it does not leak into access logs. A realtime upgrade that carries a query string is rejected.
  4. The Origin header is required (realtime): both the ticket request and the upgrade must carry Origin, and it must be one of the environment’s allowed origins. The browser adds it itself; if it is a non-browser client, you must send an allowed Origin value yourself.
  5. Module gate. The realtime socket depends on the plugins module, /v1/ws on notifications, and /v1/chat/ws on chat. If the module is disabled, the upgrade returns 404 and module_disabled; an open socket is closed with 1008 (reason module_disabled) if the module is disabled later, and with 1013 if the gate state cannot be verified. You manage modules from the panel: Game settings: modules.

Notification socket (/v1/ws)

  • It flows only from the server to the player. Frames coming from the client are ignored.
  • The incoming frame is a JSON object: {"type":"notification","notification_id":"…","title":"…","body":"…","data":{…},"channel":"in_game","sent_at_ms":1760000000000}. Field names are snake_case.
  • A live frame is delivered at most once. The durable source is the inbox; the same notification can arrive both as a frame and through an inbox query, so deduplicate by notification_id.
  • The connection counts as a presence (online) signal.

Chat socket (/v1/chat/ws)

Frames are distinguished by the type field (with camelCase values); the other field names are snake_case, and an unknown field is rejected. Example: {"type":"send","channel_id":"…","client_message_id":"retry-1","body":"hello"}.

DirectiontypeFieldsMeaning
Client → serversubscribechannel_idChannel subscription (one channel at a time)
Client → serverunsubscribe—Drops the subscription
Client → serversendchannel_id, client_message_id, bodyReplay-safe message sending, the same as REST
Client → serverpingnonce (number)Liveness probe
Server → clientsubscribed / unsubscribedchannel_id (only subscribed)Accepted
Server → clientmessagemessage, duplicateChannel message; if duplicate: true, the client_message_id had been processed before
Server → clientpongnonceThe reply to ping
Server → clienterrorcodeinvalid_frame, invalid_payload, forbidden_channel, unavailable, message_rejected, module_disabled when the module is being disabled

Message sending limit: 20 messages per 10 seconds per player; you see an overage as message_rejected or, in REST, as 429 (rate limits). For detailed usage, see the chat guide.

Next step

  • To see the plugin realtime protocol from start to finish, see the protocol.
  • To see what happens when the connection drops, the limits, and idle close, see reconnect and limits.