Topics and events
How topics are named and allowed, what an event is, and when to use stream, kv or signal delivery.
Topics
A topic is a named channel such as chat/lobby. Names are made of levels separated by /:
- each level uses only
A-Z a-z 0-9 . _ ~ : @ = + -; - at most 16 levels and 255 bytes;
- names starting with
$are reserved.
The SDK checks names before sending and throws InvalidTopic for a bad one.
Topic rules
A topic can only be used if one of the segment's topic rules matches its name. You manage rules on the segment's Topic rules tab. Each rule has:
| Field | Meaning |
|---|---|
| Pattern | Which topics it covers. * matches exactly one level; a trailing > matches one or more. |
| Class | The delivery type of events on these topics: Durable (stream), Last value (kv) or Ephemeral (signal). |
| Operations | What clients may do: subscribe, publish, signal, history, presence. |
| Limits | Maximum payload size, history size (Durable) or value lifetime and number of keys (Last value, Ephemeral). |
Rules are checked in order; the first match wins.
| Pattern | Matches | Doesn't match |
|---|---|---|
chat/> | chat/lobby, chat/team/42 | chat |
rides/*/location | rides/42/location | rides/42/eta, rides/42/x/location |
scores/* | scores/match-7 | scores/2026/final |
Events
You publish events to a topic. An event has a name you choose, data, and a delivery type:
// stream, the default
await room.publish('message', { text: 'hi' });
// kv and signal need the delivery type
await rides.publish('location', { lat: 59.43, lng: 24.75 }, { delivery: 'kv' });
await typing.publish('typing', { on: true }, { delivery: 'signal' });Subscribers receive each event as a Message with the name, the data, the
verified publisher and the time the node accepted it.
Delivery types
stream | kv | signal | |
|---|---|---|---|
| Dashboard class | Durable | Last value | Ephemeral |
| What the node keeps | Every event, in order, up to the topic's history limit (5 MiB by default, 24 hours at most) | The latest value per key | Each sender's latest signal, for 5 seconds |
publish() resolves with | A receipt, once stored | A receipt, once stored | Nothing; it is fire-and-forget |
| While offline | Queued and sent on reconnect | Queued and sent on reconnect | Dropped |
| A new subscriber gets | History from where it asks, then live events | The current values, then changes | The current signals, then live ones |
| Use it for | Chat, feeds, orders, logs | Locations, read receipts, status, scores, settings | Typing indicators, live cursors, "is viewing" |
| Guide | Publishing | Key-value (kv) events | Signals |
stream: an ordered log
Every event is kept, in order, and can be replayed. A subscriber can start from the latest event, the oldest one still kept, the last N, or right after a cursor it saved. Each stored event gets:
- a
cursor("epoch:seq"): its position, used to resume; - a
uid: a permanent id. Point replies, edits and reactions at theuid, never at the cursor: cursors can change if the node restarts, uids don't.
kv: the latest value per key
Each event has a key; a new value for the same key replaces the old one. By default the key is your identity,
so publish('location', …, { delivery: 'kv' }) from driver:7 sets the value under driver:7. Values can expire
(ttlMs) or be removed (remove: true). New subscribers first receive a snapshot of every current value.
signal: short-lived and fire-and-forget
Signals are not stored or acknowledged. The node keeps each sender's latest signal for 5 seconds so a new subscriber sees who is typing right now. Signals are dropped while you are offline, and anonymous connections can't send them.
One delivery type per topic, for now
Today, each topic rule has one class
A topic rule in the dashboard has a single class, so every event on a topic uses that rule's delivery type. Put
each type on its own topics, for example chat/{room} (Durable) for messages and typing/{room} (Ephemeral) for
typing, and pass the matching delivery when you publish. Publishing a type the rule doesn't allow fails with
ClassMismatch.
The SDK sends every event as stream unless you say otherwise, so always pass { delivery: 'kv' } or
{ delivery: 'signal' } for those topics, or fix the delivery once with typed topic definitions.
A rule set that matches the examples in these docs:
| Pattern | Class | Operations |
|---|---|---|
chat/> | Durable | subscribe, publish |
rides/*/location | Last value | subscribe, publish |
typing/> | Ephemeral | subscribe, signal |
scores/> | Last value | subscribe, publish (your server writes scores; viewers' tokens only get subscribe) |
Payloads
| You pass as data | Sent as | Subscribers get in msg.data |
|---|---|---|
An object, array, number, boolean or null | JSON | The parsed value |
| A string | Text | The string |
A Uint8Array, ArrayBuffer or typed array | Bytes | A Uint8Array |
To label binary formats, pass contentType: 'flatbuffers', 'protobuf', 'msgpack', 'cbor', or your own
number from 1024 up. The node never reads payloads.
A payload can be up to session.limits.maxPayloadBytes (60 KiB unless your segment sets less). You can add up to 16
string headers per event; subscribers receive them unchanged.