Channels
Channels are the publish/subscribe routing layer. A peer subscribes to a channel; publishers to that channel reach every subscriber except themselves.
Channel naming
A channel name is any printable string. Conventions that work well:
| Pattern | Example | Use case |
|---|---|---|
<app_id>/<resource> | app_abc/room-1 | Per-app namespacing, multiple resources |
<feature>/<id> | chat/12345, call/xyz | Per-feature isolation |
<feature>/<id>/<channel> | room/xyz/cursor, room/xyz/chat | Sub-channels within a resource |
The server doesn't impose structural rules — you can use any printable characters except control bytes. Names are case-sensitive (Room-1 ≠ room-1).
Length cap: 256 bytes UTF-8.
Authorization — channelPatterns
Every key (pk or sk) has a channelPatterns list configured at create time. A peer can only subscribe/publish/send-related-to channels that match at least one pattern.
Patterns support two wildcards:
| Glob | Matches |
|---|---|
* | Any sequence of characters EXCEPT / |
** | Any sequence including / |
\* / \\ | Literal * or \ (escape) |
Examples:
| Pattern | app_abc/room-1 | app_abc/sub/deep | other/x |
|---|---|---|---|
app_abc/* | ✅ | ❌ (the / blocks *) | ❌ |
app_abc/** | ✅ | ✅ | ❌ |
** | ✅ | ✅ | ✅ |
app_abc/room-* | ✅ | ❌ | ❌ |
Tip: Set the most-restrictive pattern that still works. A pk_ key configured with app_abc/* rather than ** can't be misused to fan into other customers' channels if the key leaks.
Subscribing
{
"type": "subscribe",
"channel": "app_abc/room-1",
"requestId": "sub-1"
}
Server reply:
ack { requestId: "sub-1" }on success.- One-shot
presence { joined: [...] }listing every peer already in the channel when you joined (the server stamps these from each peer'speerMetadataJWT claim). - Your
subscribetriggers apresence { joined: [{ peerId: "you" }] }broadcast to every other subscriber.
Opt-in per-message metadata stamping
{
"type": "subscribe",
"channel": "app_abc/room-1",
"includeSenderMetadata": true,
"requestId": "sub-1"
}
When includeSenderMetadata is true, every message you receive on this channel includes the sender's peerMetadata under fromMetadata. Default is false to keep wire bytes down — chat use cases want this on, high-frequency cursor sync usually doesn't. See Presence & Metadata for the trade-offs.
Publishing
{
"type": "publish",
"channel": "app_abc/room-1",
"data": { "anything": "json" },
"requestId": "pub-1"
}
- Fan-out is every subscriber except the publisher.
datais any JSON value — opaque to the server.- Server returns
ackonce accepted into the broadcast queue (not when every subscriber has received it). - The publisher does not need to be subscribed to the channel they're publishing to — common for backends that publish via REST without ever connecting.
Unsubscribing
{ "type": "unsubscribe", "channel": "app_abc/room-1", "requestId": "unsub-1" }
The channel's other subscribers see a presence { left: [{ peerId: "you" }] }. Disconnecting (closing the WebSocket) is equivalent to unsubscribing from every channel you're on.
Server-side operations
Channels are also reachable from your backend via the REST API:
GET /v1/channels/:id/peers— list who's currently subscribedPOST /v1/channels/:id/publish— publish to a channel from your backend (no WS required)
Server-side publish is the typical pattern for IoT command-and-control, AI agent fan-out from a workflow service, and chat moderation actions ("admin broadcasts").
Reserved namespaces
The following channel-name prefixes are reserved for platform use. Customer keys can NEVER interact with them regardless of channelPatterns:
_metered/— platform-issued events_internal/— cross-instance server-to-server routing_system/— operational broadcasts
Prefix matching is case-insensitive, so _METERED/foo is also rejected. Subscribing or publishing to a reserved channel returns error: channel_reserved immediately.
Per-connection limits
| Limit | Default | Why |
|---|---|---|
| Channels per connection | 100 | Anti-abuse; matches typical app patterns where one user is in a handful of rooms. |
| Inbound message bytes | 64 KB | WS frame cap; larger frames are closed with code 1009. |
| Publish rate | Per-plan (see Rate Limits) | Token bucket; close code 4011 on sustained abuse. |
What's next
- Presence & Metadata — the
peerMetadata/metadatastory in full - REST API → Channels — server-side publish + peer listing