Protocol 1.4

Wire protocol

Every frame in both directions is a single JSON object with an op field. There is no binary framing and no custom compression — the transport is a plain WebSocket, so a proxy, a browser devtools panel or wscat can read the traffic as-is.

Handshake

Open wss://edge.zorelium.site/connect?v=1.4. The gateway sends hello first, then waits up to 10 seconds for auth. Unauthenticated sockets are closed with 4401.

<- server
{ "op": "hello", "sid": "c8f0a1e2", "proto": "1.4", "ping_s": 25 }

-> client
{ "op": "auth", "token": "eyJhbGciOi…" }

<- server
{ "op": "ready", "app": "acme", "exp": 1794000000 }

Tokens are JWTs minted by your backend with the app secret; the gateway checks the signature, the exp claim and the channel scopes. Mint them short — an hour is plenty, twelve is the ceiling.

Subscribing

{ "op": "subscribe", "channels": ["room:42"], "last_seq": 90714 }

Passing last_seq asks for a replay of everything the channel emitted after that sequence number, as long as it is still inside the retention window. Frames arrive in order per channel; ordering across channels is not guaranteed and never has been.

Frames

opDirectionMeaning
helloserverSession opened, carries the ping interval.
authclientPresents a token. Exactly once per connection.
readyserverToken accepted, scopes resolved.
subscribe / unsubscribeclientAdjusts the channel set. Idempotent.
msgserverA published frame: ch, seq, ts, data.
presenceserverJoin/leave deltas on presence:* channels.
ping / pongbothLiveness. Miss two and the socket is closed.
errorserverNon-fatal problem; the socket stays open.

Close codes

CodeMeaningWhat to do
4400Malformed frameFix the client; reconnecting will not help.
4401Missing or expired tokenMint a new token, reconnect.
4403Channel not in scopeWiden the scopes when minting.
4429Rate limit trippedBack off; the body carries retry_after.
4503Gateway drainingReconnect with last_seq; a deploy is in progress.

REST surface

GET/v1/statuspublic
GET/v1/timepublic
GET/v1/regionspublic
POST/v1/publishbearer
GET/v1/channelsbearer
GET/v1/channels/{name}/presencebearer
POST/v1/tokensbearer, app secret

The machine-readable description lives at /openapi.json. Calls without a usable bearer token answer 401 with a JSON body — including calls to paths that do not exist, because authentication is resolved before routing.

Reconnect with jitter. A gateway restart wakes every client at once, and a synchronised stampede is the one load pattern that has ever taken this service down — see the changelog entry for 1.3.5.