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

Chat

This page describes channel chat between players (global, world, clan, party and direct). Read it if you want to send messages with a player session, read history and receive live messages.

What it does

The chat module (chat) provides channel-based text chat. Messages are written to a durable archive; live delivery is done separately over a WebSocket. The same message can be sent over REST or over the WebSocket, and both apply the same rules (mute, rate limit, profanity filter, replay protection).

It works with the player’s own session. Chat endpoints cannot be called with an API key.

If the module is disabled, the endpoints return 404 with {"code":"module_disabled","module":"chat"}; the chat socket first sends an error frame (code: "module_disabled") and then closes. Enable the module on the Game settings › Modules page.

Where it lives in the panel

What you want to doPanel page
Enable or disable the moduleGame settings › Modules
Read the chat of the city (scene) plugin, delete a message, mute a playerCity chat

Note

Panel help pages are not translated yet; the panel links above point to the Turkish source. The panel’s “City chat” screen manages the chat of the scene plugin; for the channel chat on this page there is no panel screen yet for reading, deleting or muting messages.

Data model (in plain words)

  • Channel: Distinguished by kind:

    kindreference_idWho can join
    globalnoneEveryone in the environment
    worldrequired (world/map UUID)Players who know the reference
    clanrequired (clan UUID)Members of the clan
    partyrequired (party UUID)Members of the party
    directnone; direct_player_id is requiredOnly the two players

    A channel of the same kind and the same reference is created once; asking again returns the existing channel. In a direct channel the other player cannot be yourself.

  • Message: id, channel_id, sender_player_id, client_message_id, body, sent_at_ms.

  • client_message_id: A replay key generated by the client (cannot be empty, at most 200 characters). A second send with the same value in the same channel does not create a new message; it returns the existing message with duplicate: true.

  • Mute: Muting a player in the scope all, global, world, clan, party or direct. A send by a muted player returns 403.

Calls your game makes

All chat endpoints are called with a player session: the session cookie or an opaque session token (Authorization: Bearer <session token>), plus the environment header:

x-crx-env: <environment uuid>

On these endpoints the x-csrf-token double-submit and the Origin check are not applied; the session cookie or session token is enough (unlike the store purchase endpoint). Session rules: Sessions and CSRF.

Method and pathWhat it does
POST /v1/chat/channelsCreates a channel or returns the existing one
GET /v1/chat/channelsLists the channels the player is authorized for
POST /v1/chat/channels/{channel_id}/messagesSends a message
GET /v1/chat/channels/{channel_id}/messagesReads the archive backwards (before_ms, limit 1–100, default 50)
GET /v1/chat/wsLive chat WebSocket

Note

The TypeScript and Unity SDKs have no ready-made client for chat; call the endpoints with raw REST and WebSocket. See TypeScript SDK.

Creating a channel

POST /v1/chat/channels
x-crx-env: <environment uuid>
Content-Type: application/json

{ "kind": "clan", "reference_id": "0192f0aa-…" }
{ "channel": { "id": "0192f2bb-…", "environment_id": "…", "kind": "clan", "reference_id": "0192f0aa-…", "participant_a": null, "participant_b": null, "created_at_ms": 1790000000000 } }

A player who is not a member of the clan or party is rejected when creating or reading that channel.

Sending a message

POST /v1/chat/channels/0192f2bb-…/messages
x-crx-env: <environment uuid>
Content-Type: application/json

{ "client_message_id": "c-0001", "body": "Hello!" }
{ "message": { "id": "…", "channel_id": "0192f2bb-…", "sender_player_id": "…", "client_message_id": "c-0001", "body": "Hello!", "sent_at_ms": 1790000000123 }, "duplicate": false }

The rules, in order:

  1. 400 if client_message_id is empty or longer than 200 characters.
  2. The body must be 1–2000 Unicode characters and contain at least one non-whitespace character; otherwise 400.
  3. 403 if the player is muted for that channel.
  4. Rate limit: at most 20 messages per player per 10 seconds; exceeding it returns 429.
  5. Profanity filter: if the message contains a banned word (a whole word separated by whitespace, case-insensitive), 422 is returned.
  6. The message is written to the archive, then delivered to live subscribers. Even if delivery fails, the archived message is not deleted.

Reading the archive

GET /v1/chat/channels/{channel_id}/messages?before_ms=1790000000000&limit=50 returns { "messages": [ … ] }. before_ms asks for messages older than this Unix millisecond timestamp; for the next page, pass the sent_at_ms of the oldest message.

Live chat (WebSocket)

When connecting to GET /v1/chat/ws, x-crx-env and the session identity (cookie or Authorization: Bearer) are sent as headers. The upgrade is not performed without authentication (401); 404 if the module is disabled.

Warning

The browser WebSocket API cannot send custom headers (x-crx-env); therefore the chat socket cannot be connected directly from a browser. Browser games can poll with REST; native clients (for example Unity desktop/mobile) can connect by adding headers. A query parameter or subprotocol instead of a header is not supported yet.

Client frames (JSON; the type field is lower camelCase, the other fields are snake_case):

typeFieldsMeaning
subscribechannel_idSubscribe to the channel’s live stream
unsubscribe—Drop the subscription
sendchannel_id, client_message_id, bodySend a message, as in REST
pingnonceLiveness probe

Server frames:

typeFields
subscribedchannel_id
unsubscribed—
messagemessage, duplicate
pongnonce
errorcode (invalid_frame, invalid_payload, forbidden_channel, unavailable, message_rejected, module_disabled)

A socket is subscribed to a single channel at a time. A frame with an unrecognized field gets invalid_frame. If the module is disabled later, the open socket is closed too. For the general realtime surface, see Realtime: overview.

Versioning and replay safety

  • Message sending has no Idempotency-Key header; replay safety is provided by client_message_id. After a network error, resend with the same value: the message appears once (duplicate: true).
  • If-Match is not used.

Events

A chat event is not delivered as a webhook; chat is not among the webhook event types. Use the WebSocket for the live stream.

Limits

  • Message body 1–2000 characters; client_message_id at most 200 characters.
  • Send rate: 20 messages per player per 10 seconds.
  • An archive page is 1–100 messages.
  • A WebSocket can be subscribed to a single channel at a time.

Common errors

SymptomCauseFix
401No session, expired, or x-crx-env missingRefresh the session; add the header
403 (send)The player is muted or has no permission in the channelInform the user
400Empty or too long body / client_message_idFix the input
422The profanity filter rejected the messageChange the message
42920 messages per 10 seconds exceededWait and retry
error: forbidden_channel (WS)No permission for the channelCreate the channel with REST first / verify the permission

Working example

Writing to a channel over REST with a session cookie (cookies.txt) and reading the history:

curl -X POST "$API/v1/chat/channels/$CHANNEL/messages" \
  -H "x-crx-env: $ENV" \
  -H "Content-Type: application/json" -b cookies.txt \
  -d '{"client_message_id":"c-0001","body":"Hello!"}'

curl "$API/v1/chat/channels/$CHANNEL/messages?limit=20" \
  -H "x-crx-env: $ENV" -b cookies.txt