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
| op | Direction | Meaning |
|---|---|---|
| hello | server | Session opened, carries the ping interval. |
| auth | client | Presents a token. Exactly once per connection. |
| ready | server | Token accepted, scopes resolved. |
| subscribe / unsubscribe | client | Adjusts the channel set. Idempotent. |
| msg | server | A published frame: ch, seq, ts, data. |
| presence | server | Join/leave deltas on presence:* channels. |
| ping / pong | both | Liveness. Miss two and the socket is closed. |
| error | server | Non-fatal problem; the socket stays open. |
Close codes
| Code | Meaning | What to do |
|---|---|---|
| 4400 | Malformed frame | Fix the client; reconnecting will not help. |
| 4401 | Missing or expired token | Mint a new token, reconnect. |
| 4403 | Channel not in scope | Widen the scopes when minting. |
| 4429 | Rate limit tripped | Back off; the body carries retry_after. |
| 4503 | Gateway draining | Reconnect with last_seq; a deploy is in progress. |
REST surface
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.