PieSocket Protocol V4
Low-level reference for PieSocket's V4 WebSocket wire protocol, useful if you're building your own client library instead of using an official SDK. V4 speaks the same free-form pub/sub model as V3, plus multiplexing: one connection can subscribe to multiple channels at once.
Connecting
wss://CLUSTER_ID.piesocket.com/v4/CHANNEL_ID?api_key=API_KEY
Same query parameters as V3 (api_key, notify_self, user, uuid, jwt, presence), with one difference: V4 has no binary parameter, any raw binary frame you send is detected and handled automatically, see Binary messages below.
The channel in the URL becomes your connection's primary channel. A V4 connection can subscribe to further channels at runtime, see Multiplexing below.
Connection lifecycle
Same as V3: the socket opens, then the server validates your API key, cluster status, and JWT, and either keeps the connection open or sends an error frame and closes it.
V4 uses double-colon event names throughout (system::error, system::boot, and so on) instead of V3's single colon.
Requesting a boot event
Pass be=1 (or a be header) to receive this frame right after a successful connection:
{ "event": "system::boot" }
Sending & receiving messages
Same free-form model as V3, send any string or JSON payload, PieSocket doesn't enforce a schema. It's broadcast to every other connection on that channel (and back to you if notify_self=1). See Events for the conventional {event, data} shape our SDKs use, and V3's Addressing a specific user for system::to.
One difference from V3: every JSON message you receive over V4 has a system::channel field added automatically, naming the channel it was published on:
{ "event": "new-message", "data": "Hello!", "system::channel": "room-1" }
This lets a single multiplexed connection tell which of its subscribed channels an incoming message belongs to.
Messages are limited to your PieSocket cluster's configured size limit (3MB by default). Publishing from a client requires client-to-client messaging to be enabled for your API key, same as V3, this doesn't apply to the control frames below (subscribe/unsubscribe/get members).
Multiplexing
A V4 connection isn't limited to the single channel from its connection URL, subscribe to additional channels over the same socket at runtime.
Subscribe
{
"event": "system::subscribe",
"data": { "channel": "room-2", "user": "user-123", "presence": true }
}
Reply on success:
{ "event": "system::subscribe_success", "data": { "channel": "room-2" } }
Reply on failure:
{ "event": "system::subscribe_error", "data": "<reason>" }
Private channels are checked with the same JWT rules as the primary connection, include a jwt field in data if the channel requires one. A failed subscribe only rejects that one channel, your other subscriptions stay connected.
Channel names must be 1-200 characters, with no /, `, or control characters. user/uuid` values are capped at 512 characters. A single connection can hold up to 100 subscribed channels.
Unsubscribe
{ "event": "system::unsubscribe", "data": { "channel": "room-2" } }
Reply: { "event": "system::unsubscribe_success", "data": { "channel": "room-2" } }
Your primary channel, the one from the connection URL, can't be unsubscribed, close the connection instead.
Publishing to a secondary channel
Include a top-level system::channel key to target a channel other than your primary one:
{
"event": "new-message",
"data": "Hello room 2!",
"system::channel": "room-2"
}
You must be subscribed to that channel first.
Refreshing membership on demand
{ "event": "system::get_members", "data": { "channel": "room-2" } }
Reply: { "event": "system::member_list", "data": { "channel": "room-2", "members": [...], "count": 5 } }
Subscribe/unsubscribe/get_members calls are rate-limited per connection, a short burst is fine, sustained spamming isn't. Exceeding it gets you:
{ "event": "system::error", "data": "Rate limit exceeded, slow down control frames" }
Errors
{ "event": "system::error", "data": "<reason>" }
Same causes as V3: an invalid or unknown API key, a disabled cluster, a missing/invalid JWT, a disallowed origin, or client-to-client messaging being disabled.
Presence channels
Channels prefixed presence- (or subscribed with presence: true) track membership. Unlike V3, members are deduplicated by identity (uuid, or user if no uuid is given), so the same user connected from two devices or tabs counts once, and every presence payload includes the channel and a distinct-member count:
{ "event": "system::member_list", "data": { "channel": "room-1", "members": ["user-1", "user-2"], "count": 2 } }
{ "event": "system::member_joined", "data": { "channel": "room-1", "member": "user-3", "count": 3 } }
{ "event": "system::member_left", "data": { "channel": "room-1", "member": "user-2", "count": 2 } }
member_joined/member_left only fire on a given identity's first/last active connection, reconnecting the same identity from a second tab won't re-fire member_joined. Anonymous connections (no user/uuid supplied) are never deduplicated. Use system::get_members any time you need a fresh roster without waiting for a join/leave event.
Private channels
Same rules as V3, applied per-channel, both for your primary connection and for any channel you subscribe to via system::subscribe.
Binary messages
No opt-in required, any raw binary WebSocket frame you send is detected automatically and broadcast to every other connection on that channel as:
{ "event": "system::binary", "system::channel": "room-1", "data": "<base64-encoded payload>" }
Built-in commands
Same as V3, send as plain text frames, the reply goes only to the sender:
| Command | Reply |
|---|---|
| cmd_status | {"system":{"connection_count": N}} |
| cmd_ping | {"system":"pong"} |
Publishing over REST
A backend can publish without opening a WebSocket connection at all, point it at the V4 path:
POST https://CLUSTER_ID.piesocket.com/api/v4/publish
{
"key": "YOUR_API_KEY",
"secret": "YOUR_API_SECRET",
"roomId": "room-1",
"message": { "event": "new-message", "data": "Hello" }
}
The same path has a presence lookup too, at POST /api/v4/members, with roomId required and the response deduplicated by identity, matching Presence channels above. Full parameters, responses, and the rest of the account-level API are in the REST API reference.
Webhooks
Independently of any client connection, your cluster can POST every message (and, optionally, presence events) to an endpoint on your own server as it happens. On V4, messages are batched, a single request can carry up to 250 messages:
{
"apiKey": "your-api-key",
"messages": [
{ "channel": "room-1", "message": "Hello world", "timestamp": 1732000000000 }
]
}
See PieSocket Webhooks for setup and the full payload format.
