Skip to main content

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:

PatternExampleUse case
<app_id>/<resource>app_abc/room-1Per-app namespacing, multiple resources
<feature>/<id>chat/12345, call/xyzPer-feature isolation
<feature>/<id>/<channel>room/xyz/cursor, room/xyz/chatSub-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-1room-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:

GlobMatches
*Any sequence of characters EXCEPT /
**Any sequence including /
\* / \\Literal * or \ (escape)

Examples:

Patternapp_abc/room-1app_abc/sub/deepother/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's peerMetadata JWT claim).
  • Your subscribe triggers a presence { 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.
  • data is any JSON value — opaque to the server.
  • Server returns ack once 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 subscribed
  • POST /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

LimitDefaultWhy
Channels per connection100Anti-abuse; matches typical app patterns where one user is in a handful of rooms.
Inbound message bytes64 KBWS frame cap; larger frames are closed with code 1009.
Publish ratePer-plan (see Rate Limits)Token bucket; close code 4011 on sustained abuse.

What's next