Skip to content

Connection lifecycle

This guide covers what happens to a connection after the quickstart: checking that you reached the right server, signing in safely, keeping the connection alive, recovering after a drop, and reacting to presence and timeout events.

Each WebSocket has its own state. Keep these per connection, and throw them away when it closes:

  • The handshake values, including validator_key and capabilities.
  • The signed-in account from ready.
  • ready.local_upload_token, if the account is a server account.
  • Pending requests and their timers.
  • Any slash commands you registered and any voice channel you joined.

The server keeps slash registrations and voice membership only while the connection that created them is open.

A Rotur validator key looks like this:

originChats-https://chat.example.com-4c1f0a9b2e7d45f8a3b6c9d0e1f2a3b4

The server builds it from originChats-, its configured server.url, a dash, and 32 random characters. Before you ask Rotur for a validator, check that the URL inside the key matches the server you meant to connect to. If it doesn’t, stop. A validator proves your identity to whoever holds the key, so a server that shows a different URL could use your validator to sign in to that other server as you.

The handshake has two more URLs:

  • server.url is the server’s configured public address. It is the same URL that appears in the validator key.
  • signing_url is the text used in signed payloads. The server sets it to the same configured URL, but treat it as its own value.

Reconnect to the address you actually connected to, not to one of these values. Server names are display text and don’t identify a server.

Send one sign-in command, auth, login, or register, and wait for the answer before you send another. Don’t try Rotur and a local login at the same time on one socket.

A successful sign-in sends these frames in order:

  1. auth_success. Other commands now work.
  2. ready, with your user object in user. Server accounts also get local_upload_token. If you are the server owner, ready also has a boolean rotur_token that says whether the server has a Rotur API token configured.
  3. users_list, only if this socket was already signed in as a different account.
  4. rate_limit, only if the account is in a timeout.

A failed sign-in sends one auth_error. Its val is text you can show. Some failures also carry a reason:

reason Meaning
banned The account is banned from this server, or its Rotur account is banned.
password_required, password_incorrect, not_whitelisted, application_required, application_pending, invite_required, invite_invalid, invite_expired The server’s access control turned you away. The frame also has the server’s access mode.

A server with access control may need extra fields on the sign-in command, such as server_password or invite_code. The handshake’s access_control object tells you which mode is active. Access control explains each mode.

If you save local credentials, save them only after ready arrives, and key them by the server URL.

Every 30 seconds the server sends this frame to every open socket, whether or not it has signed in:

{ "cmd": "ping" }

You don’t need to reply. Don’t confuse it with the response to your own ping request, which always has timing fields such as server_time or sent_at.

When a connection closes:

  1. Stop sending on it and cancel its request timers.
  2. Discard its handshake, validator key, upload token, and clock estimate.
  3. Reconnect after a delay that grows after each failure.
  4. Read the new handshake and sign in again.
  5. Reload the state you show: channels, members, unread counts, and the messages in view.
  6. Register slash commands and rejoin voice again if you need them.

The server does not replay events you missed while disconnected.

If an old connection attempt finishes after a newer one has started, ignore the old one. When the user disconnects on purpose, cancel automatic retries.

A request that never got a response may still have worked. Before resending a message or an admin action, decide what happens if the server applied it the first time.

The server tells you when other members come and go. The main events are:

Event Meaning
user_join Someone joined the server as a member.
user_leave Someone left the server or was banned.
user_connect A member came online.
user_disconnect A member went offline or became invisible.
user_clients A member’s list of apps or devices changed.
status_get A member changed their status.

user_disconnect doesn’t mean the user left the server. One account can have several connections, and it only counts as offline when the last one closes. Invisible users look offline to most viewers. Users and presence explains who sees what, and server events has the full list.

clients and devices on a user are temporary. Replace them whenever you get a full user object or a user_clients event, and clear them when the user goes offline.

When a server advertises user_metadata_v1, bot: true on a user marks an account as a bot. Don’t guess bot status from a client name or a username.

A moderator can put an account in timeout. The account stays signed in and can still read, but actions such as sending messages, reacting, and typing are refused. The protocol model lists which commands are blocked.

A refused action returns rate_limit:

{
"cmd": "rate_limit",
"src": "message_new",
"reason": "You are in timeout for 42.5 more seconds",
"length": 42500
}

length is the remaining time in milliseconds. The server also pushes rate_limit without src when a timeout starts, when a moderator removes it, and when it expires. A frame with length: 0 clears the timeout on that server.

Slow mode is a per-channel limit on how often you can post. It also answers message_new with rate_limit, but that frame has cooldown (the channel’s limit in seconds) and last_message_timestamp. Treat it as a wait for that channel only, not as an account timeout. See time fields for units.