Wire Protocol
The exact bytes on the wire — every WebSocket topic, message, HTTP endpoint, and error, across all four products.
This is the ground-truth reference for building an SDK, debugging a raw socket frame, or implementing a client in a language Pingerchips doesn't ship. It describes what the server actually sends and accepts today — not a roadmap.
The official SDKs (pingerchips-js, pingerchips-js-server, @pingerchips/ai,
pingerchips-python) wrap all of this. Read this only when you need the layer
underneath.
Transport
All real-time traffic is Phoenix Channels
over a single WebSocket. All server-to-server traffic is HTTP with
X-App-Key + X-App-Secret headers (Authentication).
| Environment | WebSocket | HTTP |
|---|---|---|
| Production | wss://queue.pingerchips.com/socket | https://queue.pingerchips.com |
| Development | ws://localhost:4000/socket | http://localhost:4000 |
Socket connection
Phoenix's socket handshake carries params in the query string:
wss://queue.pingerchips.com/socket/websocket?app_key={appKey}&vsn=2.0.0| Param | Required | Meaning |
|---|---|---|
app_key | yes | identifies the app; the connection is rejected (:error, socket closes) if unknown, over the connection limit, or from a disallowed Origin |
client_id | no | a default client id, used by channels that don't get one from a token |
vsn | yes | Phoenix wire version — use 2.0.0 |
There is no auth on the socket itself beyond app_key + Origin. Authorization
happens per-channel, in join.
The Phoenix message frame
Every frame — v2.0.0 wire format — is a 5-element JSON array:
["{join_ref}", "{ref}", "{topic}", "{event}", {payload}]| Slot | Meaning |
|---|---|
join_ref | the ref of the phx_join that opened this channel; null for messages not tied to a join |
ref | a client-chosen id echoed in the matching reply; null for server-initiated pushes |
topic | the channel topic (see each product below) |
event | the message name |
payload | an arbitrary JSON object |
Joining a channel:
["1", "1", "app:pk_live_x:room:orders", "phx_join", { "auth": "..." }]Reply (event is always phx_reply, ref matches):
["1", "1", "app:pk_live_x:room:orders", "phx_reply", { "status": "ok", "response": { "socket_id": "..." } }]status is "ok" or "error"; the channel-specific body is under response.
Heartbeat — send every ~30 s or Phoenix closes the socket:
[null, "hb-1", "phoenix", "heartbeat", {}]Topics at a glance
| Topic pattern | Channel | Product |
|---|---|---|
app:{appKey}:room:{channel} | PubSub | pub/sub messaging |
durable:{appKey}:{type}:{key} | Durable Objects | replicated state |
chat:v1:app:{appKey}:thread:{threadId} | Chat | AI chat sessions |
Spaces has no topic of its own — it runs on a PubSub room: channel (today,
one named ephemeral-{spaceId}). See Spaces.
The version segment (v1 for chat, none yet for the others) is frozen. A
breaking change bumps it — chat:v2:... — and both versions run on one socket.
See Versioning.
Authentication summary
| Path | Auth |
|---|---|
| WebSocket connect | app_key query param + Origin check |
| PubSub public channel join | none, unless the app has enable_user_authentication = 1 |
| PubSub private / presence channel join | { auth: "<capability token>" } granting channel:subscribe:{channel} |
| Durable Object channel join (browser) | { auth: "<capability token>" } granting object:read:{type}/{key} |
| Durable Object channel join (server) | { app_secret: "..." } |
| Chat thread join (client) | { auth: "<capability token>" } granting chat:subscribe:{threadId} |
| Chat thread join (agent) | { secret: "...", agent_id: "..." } |
| Every server HTTP call | X-App-Key + X-App-Secret headers |
Capability-token issuer (POST /api/auth) | X-App-Key + X-App-Secret headers |
Full model, token format, and capability grammar: Authentication.
Pages
- PubSub —
app:*channels: messages, delta compression, replay, client publish - Durable Objects —
durable:*channels + the/api/v1/objectsREST API - Spaces — cursors, member presence, locations, locks
- Chat —
chat:v1:*channels: run lifecycle, token streaming, the conversation tree - Errors & sequencing — every error code, and the ordering guarantees
- Versioning — what counts as a breaking change