---
title: "Realtime channels"
description: "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."
url: "/docs/guides/realtime/"
lang: en
index: "/llms.txt"
---
# 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](/docs/guides/multiplayer/index.md), and it is off until you turn it on:

```sh
kumodeck features on realtimeChannels
kumodeck config push --env development
```

**KUMODeck 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](/docs/guides/functions/index.md) and database (see [Keeping a history](#keeping-a-history)).

## Join and send

```js
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](#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`.

```js
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, unban
```

To 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](/docs/guides/voice/index.md).

## From your server (secret key)

Your server (for example your [Functions](/docs/guides/functions/index.md)) 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 `op`s 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.
