Realtime channels
A realtime channel is a named room where every message reaches everyone inside instantly: a team workspace, a live event, a help desk, or in a game a lobby, a party or a match. Build text messages, reactions, typing indicators, "ready" signals or live notices on top of it. It uses the same connection as multiplayer rooms, and it is off until you turn it on:
kumodeck features on realtimeChannels
kumodeck config push --env developmentKUMODeck does not store messages. A message is delivered to the people in the channel and then dropped: no database, no logs of the text, no recordings. If your app should keep a history, save it in your own Functions and database (see Keeping a history).
Join and send#
const ch = await kumo.realtime.join('lobby'); // joins; a channel without "@" is created on first join
ch.on('message', ({ from, type, data, at }) => { … }); // from = the sender's player id ('server' from your server)
ch.send('msg', { text: 'hi' }); // to everyone else in the channel
ch.members; // [{ id, displayName, anonymous }]
await ch.leave();send is fire-and-forget; await it to learn about muted, send_forbidden, rate_limited or payload_too_large.
from is set by the server from the user's sign-in (users are called players in the API), so a user cannot send under another user's player id. Display names
are a different matter (see Safety rules).
| Event | Payload |
|---|---|
message | { from, type, data, at } (at = server time in ms) |
playerJoined / playerLeft | member / { playerId, reason: 'left' | 'disconnected' | 'kicked' | 'banned' | 'time_limit' } |
self | { canSend, mutedUntil } — you were muted, unmuted or allowed to speak |
closed | { reason } — left, kicked, banned, deleted, time_limit, unavailable, connection_lost, replaced, unauthorized |
kumo.realtime.channels lists the channels you are in; kumo.realtime.on('connection', …) reports the connection.
After a dropped connection the SDK reconnects and joins the same channels again. Messages sent while you were away
are not replayed.
Kinds of channels#
| Name | Made by | Owner | Who can join / send |
|---|---|---|---|
no @ (lobby, team-12) | the first user who joins | none | anyone / anyone |
@…, created by a user | kumo.realtime.create({ join, send, maxMembers }) | that user | 'anyone' or 'invited' (the owner's lists) |
@…, set up by your server | PUT /v1/realtime/channels/@… (secret key) | none | your lists (allow, speakers); can be open to users without a player id |
A channel starting with @ is never created by joining: if it does not exist, joining fails with channel_not_found.
const party = await kumo.realtime.create({ join: 'invited' }); // name is made for you: share party.name
await party.invite(playerId);
// the owner can also: uninvite, allowSend / disallowSend, mute(id, { seconds }), unmute, kick, ban, unbanTo let the users talk, create the channel with voice: true (up to 100 people; from 9 the call goes through a relay server): see Voice calls.
From your server (secret key)#
Your server (for example your Functions) can set up channels, speak in them, and mute, kick or ban in them with the secret key. These calls are not available to delegated tokens.
| Body | Answer | |
|---|---|---|
PUT /v1/realtime/channels/:name (@ names) | { join?, send?, open?, maxMembers?, allow?, speakers? } | settings, lists and who is in it |
GET /v1/realtime/channels/:name | — | the same (never message text) |
DELETE /v1/realtime/channels/:name | — | { deleted: true } |
POST /v1/realtime/channels/:name/messages | { type, data? } | { delivered } — arrives with from: 'server' |
POST /v1/realtime/channels/:name/moderate | { op, playerId, seconds? } | {} — the same ops as the owner |
Safety rules#
- Every user has a player id by default. The SDK signs users in as guests automatically, so mute, kick, ban and the sending limits apply to each person.
- Open channels are stricter. Users without a player id can only join channels your server marked
open, and there the limits are tight automatically (fewer people, smaller messages, fewer messages, 30 minutes per visit), because someone without an id can come back as someone new. - Owners keep order in their channel. The user who created a channel can mute, kick and ban in it; your server can do the same in any channel. Users you ban from your app cannot join any channel.
- A display name proves nothing; the player id does. Display names are not unique: two users can share one, and anyone can call themselves "Admin" or take the channel owner's name. Show owner or staff marks from ids (
ownerId, a list of ids on your server,from === 'server'for your server's messages), never from the name. - KUMODeck does not record or store anything said. Only the channel settings, the lists and who is in it right now.
- What users say is your responsibility. Decide your rules (length, blocked words, who may speak), and keep a way to act quickly when someone misbehaves: mute, kick, ban.
Limits#
| Users with a player id | Open channel / no id | |
|---|---|---|
| People per channel (default / max) | 200 / 1,000 | 50 / 50 |
Message size (data) | 4 KB | 1 KB |
| Messages per person | 10/s (burst 20) | 1/s (burst 3) |
| Messages per channel | 50/s (burst 100) | 10/s (burst 20) |
| Time in a channel | no limit | 30 minutes (time_limit) |
| Channels per connection | 8 | 2 |
From your server: 600 messages and 300 other calls (set-up, mute, kick, ban) per minute per key.
Keeping a history#
Every new project from a template comes with a chat Skill (a recipe for your AI agent) in .claude/skills/chat/. Ask your agent for
"a lobby chat with history": it asks you a few questions (which channels, how messages are checked, length, rate and blocked words),
then adds the code to your own Functions and database. Your server checks who sent each message with
players.verify before saving it, so the saved player id is always the real sender, and asks the channel first, so a
user muted or banned in the channel (or not in it) cannot post through your server either. The code and the data are yours.
Cost#
Billed at cost, like multiplayer rooms (no markup). A channel where someone is always talking costs about 0.6 cents per hour, however many people are in it; a quiet channel costs almost nothing.