Livqeno Chat adds real-time chat to your app. Messages are saved on our servers and delivered live to everyone in the conversation. You use one SDK; we run the servers, so you never build a WebSocket server, a message queue, or reconnect logic.
Where this fits: your backend creates a conversation, adds users as members, and makes each user a chat token. Your app connects with that token and sends and receives messages. If you haven't set up a project and API key yet, start with the Quickstart.
What you can build
- Support chat between a customer and an agent.
- Group chat and team channels.
- A chat panel next to a video call.
- Live chat under a live stream.
What you get
| Feature | Page |
|---|---|
| Messages (send, edit, delete) | Messages |
| Message history | Message history |
| Who's online | Presence |
| "Alice is typing…" | Typing |
| Read receipts | Read receipts |
| Emoji reactions | Reactions |
| Replies in threads | Threads |
| Files and images | Attachments |
| Deleting messages, bans, mutes, blocks, reports | Moderation |
| Conversation list with unread counts | Inbox |
| Offline sends, catch-up after reconnect | Offline and sync |
| Push notifications | Push notifications |
| Drop-in React components | UI components |
What Livqeno promises
These guarantees mean you don't have to write the hard parts of chat yourself:
- A message counts as sent only after it is saved in the database.
- If you retry a send, you never get a duplicate.
- A user who was offline gets what they missed from history.
- The saved history is the truth, not the live connection.
Start here
- Read the Quickstart. You'll send your first message.
- Learn Conversations and Members.
- Add features from the table above.
- Writing your own client without the SDK? See WebSocket protocol.
How it works
Three steps. The first two run on your backend, the last in your app.
1. Create a conversation. On your backend, once per chat (for example, once per support ticket or per team channel). You list who's in it and their role:
import { Livqeno } from '@livqeno/server';
const livqeno = new Livqeno({
apiKey: process.env.LIVQENO_API_KEY,
baseUrl: process.env.LIVQENO_API_URL, // https://api.livqeno.com
});
const conversation = await livqeno.chat.createConversation({
name: 'support-room-42',
getOrCreate: true, // safe to re-run: returns the existing one (members apply only on first create)
members: [{ userId: 'alice', role: 'ADMIN' }, { userId: 'bob' }],
});2. Make a token. On your backend, each time a user opens the chat, after you have logged them in. The token says who they are and which conversations they can reach:
const token = await livqeno.chat.createToken({
userId: 'alice',
conversations: [conversation.publicId],
expiresIn: 3600,
});3. Connect. In your browser app, with the token from step 2:
import { createChatClient } from '@livqeno/chat';
const chat = createChatClient({ token: token.token, apiUrl: token.apiUrl });
await chat.connect({ room: conversation.publicId });
chat.on('message', (message) => console.log(message.text));
await chat.sendMessage({ text: 'Hello everyone!' });That's it. You don't write new WebSocket(...), reconnect logic,
heartbeats, ordering, or duplicate checks. The SDK does it.
Chat and RTC are separate
| RTC | Chat | |
|---|---|---|
| Carries | audio, video, screen share | messages |
| SDK | @livqeno/rtc | @livqeno/chat |
| Connection | WebRTC | WebSocket |
| Token | RTC token | chat token |
| Saved? | No. Media is live only. | Yes, on Livqeno's servers |
- Use them together (a call with a chat panel) or alone.
- An RTC token doesn't work for chat, and the other way around.
- If one has a problem, the other keeps working.
- The only link between them is
Conversation.roomId, so one id can point to both.
Authorization — two independent checks
When a user tries to read or send a message, Livqeno asks two questions. The user must pass both. This way, even a valid token can't reach a conversation the user was never added to.
1. The token: who you are and what you may do. It holds the project, the user's identity, an expiry, an optional list of conversations, and a set of scopes. It is signed with its own key, separate from the dashboard and RTC keys. So a leak in one place doesn't affect the others.
2. Membership: which conversations you belong to.
Each member has a role in each conversation: MEMBER, MODERATOR, or
ADMIN. The role sets which scopes are allowed. A token can narrow
what the role allows, but never add more.
A scope is one named permission in the token:
| Scope | Lets you |
|---|---|
chat:read | Read messages, history, presence, and read receipts |
chat:send | Send, edit, and delete your own messages; react; type |
chat:moderate | Delete anyone's message |
chat:manage | Change the conversation's settings |
Conversation references
Anywhere that takes a room, you can pass any of these:
- The public id, like
conv_9WcQ4kRz1nB2xYtL - The conversation name, like
support-room-42 - The id of an attached RTC room
So chat.connect({ room }) works with Livqeno's id or your own name. Using
your own name (like support-room-42, or your ticket id) means you don't
have to store Livqeno's id in your database.
Next steps
Start with the quickstart, then add features as your app needs them:
- Quickstart
- Conversations
- Members
- Messages
- Presence
- Typing Indicators
- WebSocket Protocol: for building your own client.
Need video too? See Livqeno RTC. Building a live community? See Livqeno Live Streaming. Every stream comes with a chat conversation attached.