Chat¶
In-call chat runs over text streams on the topic lk.chat. Tele joins every live call as a hidden participant and saves each message with the call, so late joiners and your backend can read the history.
flowchart LR
A[Alice's app] -- sendText topic=lk.chat --> LK[(Media server)]
LK --> B[Bob's app]
LK --> R[Tele chat recorder<br/>hidden participant]
R --> DB[(Call history)]
Send and receive¶
Use the lk.chat topic on every platform so web, Flutter and mobile clients can talk to each other.
// send
await room.localParticipant.sendText(text, { topic: "lk.chat" });
// receive
room.registerTextStreamHandler("lk.chat", async (reader, { identity }) => {
const body = await reader.readAll();
const sender = room.getParticipantByIdentity(identity);
addMessage({ id: reader.info.id, body, userId: sender?.attributes.user_id, name: sender?.name });
});
useChat() from @livekit/components-react also works. It sends on lk.chat and listens there too. Don't register your own lk.chat handler while useChat is mounted: only one handler per topic is allowed.
await room.localParticipant?.sendText(text, options: SendTextOptions(topic: 'lk.chat'));
room.registerTextStreamHandler('lk.chat', (reader, identity) async {
final body = await reader.readAll();
// look up the participant by identity for name / attributes['user_id']
});
Requires livekit_client ≥ 2.12.0 for non-ASCII text (Bangla, emoji). The prebuilt chat widget in livekit_components uses an older format; build a small chat UI on lk.chat instead.
Sending requires the participant to be connected. All join tokens include the data permission.
History¶
Load earlier messages when a user joins, or show them on a call-detail screen afterwards:
{
"messages": [
{ "id": "01a131e2-…", "stream_id": "TS_x2…", "user_id": "bob", "name": "Bob",
"body": "হ্যালো 👋", "sent_at": "2026-10-13T09:01:12Z" }
]
}
- Paging: pass the last message's
idas?after=<id>to get the next page. Messages are ordered bysent_at, andlimitdefaults to 200 (maximum 500). - Deduplication:
stream_idmatchesreader.info.idfrom the media SDK. Use it to avoid showing a message twice when a live message and the history overlap. - Access: call members (user token) and your backend (service token) can read the history.
Guaranteed delivery (optional)¶
The recorder saves messages as they pass through the room. If your app needs a stronger guarantee, for example for compliance, it can also post each message it sends:
curl -X POST $TELE_API/v1/calls/$CALL_ID/messages -H "Authorization: Bearer $USER_TOKEN" \
-d '{"stream_id": "TS_x2…", "body": "হ্যালো 👋", "sent_at": "2026-10-13T09:01:12Z"}'
Messages are deduplicated by stream_id within the call: 201 means newly stored, and 200 means it was already recorded. The sender is always the user in the token.
Limits¶
- Messages are recorded up to 4,000 characters; longer messages are truncated in the history. The fallback endpoint rejects them.
- Messages from participants who aren't Tele users (such as bots) aren't recorded.