An event is Livqeno telling your code that something happened: a person joined a call, a message was sent, a stream ended. This page lists every event in one place, so you can find the right one before you write code.
Events reach you in two very different ways:
- Client events arrive in your user's app (browser or phone), while that app is open. You use them to update the screen.
- Webhooks are HTTP requests Livqeno sends to your backend, even when no app is open. You use them to save data or trigger work.
There are three lists:
| Where | Who receives it | How many | Details |
|---|---|---|---|
| RTC client events | Your app, with room.on(...) | 17 | RTC events |
| Chat client events | Your app, with chat.on(...) | 15 | Chat events |
| Webhooks | Your backend, over HTTP | 17 | Below, and Webhooks |
Which one should I use?
- Should it stop when the user closes the tab? Use a client event. Example: showing "Bob joined" in the call.
- Must it happen no matter what? Use a webhook. Example: sending a push notification for a new message, or saving a stream's end time to your database.
Webhook events
All 17 types. Each one is checked against the code. Your backend receives them at the URL you registered, signed so you can check they're really from Livqeno (see Webhooks).
Chat
| Event | Payload | Sent when |
|---|---|---|
message.created | { message } | A message is saved |
message.updated | { message } | A message is edited |
message.deleted | { messageId, roomId, ... } | A message is deleted |
reaction.added | { messageId, roomId, userId, emoji, at } | A reaction is added |
reaction.removed | { messageId, roomId, userId, emoji, at } | A reaction is removed |
message.flagged | { flagId, messageId, roomId, senderId, reporterId, reason, note, at } | A member reports a message. The message text is not included; fetch it with your API key |
member.banned | { roomId, userId, by, reason, expiresAt, appWide, at } | A user is banned from a conversation, or app-wide when appWide is true (roomId is then null) |
member.unbanned | { roomId, userId, by, reason, expiresAt, appWide, at } | A ban is lifted |
member.muted | { roomId, userId, by, reason, expiresAt, appWide, at } | A moderator mutes a user: they can read but not post |
member.unmuted | { roomId, userId, by, reason, expiresAt, appWide, at } | A mute is lifted |
Conversation membership
| Event | Payload | Sent when |
|---|---|---|
room.created | { roomId, name, type, rtcRoomId?, rtcRoomName?, createdAt } | A conversation is created. roomId is the conversation's id, not an RTC room |
participant.joined | { roomId, userId, role, at } | Someone joins a conversation |
participant.left | { roomId, userId, role, at } | Someone leaves a conversation |
RTC rooms
These are the real call rooms (/v1/rooms), not chat conversations. Every
payload's roomId is the RTC room's id.
| Event | Payload | Sent when |
|---|---|---|
room.started | { roomId, roomName, at } | The first participant joins an empty room |
room.ended | { roomId, roomName, durationMs, at } | The last participant leaves, or the room is closed |
room.participant_joined | { roomId, identity, connectionId, at } | Someone joins the call |
room.participant_left | { roomId, identity, connectionId, reason, at } | Someone's session ends. reason is LEFT, DISCONNECTED, REPLACED, REMOVED, ROOM_CLOSED, TOKEN_EXPIRED, TOKEN_REVOKED or NODE_LOST (the media server went away) |
room.track_published | { roomId, identity, trackId, kind, source, at } | Someone starts sending a track |
room.track_unpublished | { roomId, identity, trackId, at } | Someone stops sending a track |
- A participant whose connection drops and who resumes the session
produces no
participant_left/participant_joinedpair. One who doesn't come back is reported asDISCONNECTED, up to about 20 s after the drop, andatis when the connection went away. - Livqeno's own participants (recording and restream workers, relays) and internal live-streaming edge rooms never produce these events.
Live streaming
| Event | Sent when |
|---|---|
live_stream.created | A stream is created |
live_stream.started | The stream goes from CREATED to LIVE |
live_stream.ended | The stream goes from LIVE to ENDED. This is final |
live_stream.host_joined | A host or co-host is added |
live_stream.host_left | A co-host is removed |
live_stream.viewer_joined | A viewer token is created |
live_stream.viewer_left | Your backend called leave() for a viewer |
live_stream.broadcast_ready | A broadcast-mode stream can be played from the CDN: { streamId, playbackUrl, at }. Sent once, when playback really works |
live_stream.egress_failed | The video encoder for a broadcast-mode stream failed or stopped responding: { streamId, reason, at } |
live_stream.recording_ready | A broadcast-mode stream ended and its recording is playable: { streamId, recordingUrl, durationSec, at }. recordingUrl is an HLS VOD playlist of the whole broadcast |
live_stream.stage_invited | A viewer was invited on stage: { streamId, identity, at }. Their client also sees a stage event |
live_stream.stage_promoted | A viewer was brought on stage as a co-host: { streamId, identity, role, at } |
live_stream.stage_demoted | A co-host was taken off stage; their publishing stops at once: { streamId, identity, at } |
live_stream.restream_active | A restream destination started receiving video: { streamId, restreamId, label, at } |
live_stream.restream_failed | A restream destination gave up after 10 failed attempts. The stream and other destinations carry on: { streamId, restreamId, label, reason, at } |
live_stream.ingress_connected | An OBS or encoder ingest started publishing: { streamId, ingressId, protocol, at } |
live_stream.ingress_disconnected | The encoder left and didn't reconnect within the reconnect window: { streamId, ingressId, protocol, reason, at } |
live_stream.ingress_failed | Livqeno refused the encoder's media, for example B-frames: { streamId, ingressId, protocol, reason, at } |
Payloads, and a note about viewer_left, are in
Stream events.
AI
| Event | Payload | Sent when |
|---|---|---|
transcription.started | { transcriptionId, roomId, language, at } | The transcriber joined the room and is listening |
transcription.ended | { transcriptionId, roomId, language, at, durationMs } | The transcription stopped: you stopped it, the room ended, or nobody published audio for 10 minutes |
transcription.failed | { id, roomId, reason, at } | The transcription failed, for example its worker went silent |
transcript.ready | { transcriptionId, roomId, segmentCount, url } | A stored transcript is complete. Sent after the end when persist is on. url is GET /v1/transcripts/:id |
agent.started | { agentId, roomId, at } | A voice agent joined the room |
agent.ended | { agentId, roomId, at, durationMs, reason } | The agent left. reason is requested, room_ended, no_audio_publishers, max_duration or worker_ended |
agent.failed | { id, roomId, reason, at } | The agent failed |
Room recording
| Event | Payload | Sent when |
|---|---|---|
recording.started | { recordingId, roomId } | The recorder joined the room and is writing files |
recording.ended | { recordingId, roomId, durationSec } | The recording stopped: you stopped it, the room closed, or it was empty for 2 minutes |
recording.ready | { recordingId, roomId, durationSec, files } | The files are on storage. files is [{ kind: "hls" | "mp4", key, url, sizeBytes? }] |
recording.failed | { recordingId, roomId, reason } | The recording failed, for example its worker stopped reporting |
Webhook endpoint
| Event | Payload | Sent when |
|---|---|---|
webhook.test | { webhookId, at } | You press Send test event in the dashboard, or call POST /v1/projects/:projectId/webhooks/:webhookId/test. Goes only to that endpoint, whatever events it subscribes to |
What is not a webhook
These are client events only:
- Mute and unmute
- Connection state changes
- Presence
- Active speakers (
activeSpeakersChanged, see RTC events) - Participant metadata and attribute changes (
participantMetadataChanged)
(Who joins and leaves an RTC room, and what they publish, are webhooks: see RTC rooms above.)
Need one of these on your server? Poll for it instead. Use the Observability API for connection history, or the REST API for the rest.
Who is speaking is a client event only: every SDK has
activeSpeakersChanged (loudest first, at most 8, at most every 300 ms).
There is no active-speaker webhook.
Subscribe to webhooks
Most people register an endpoint in the dashboard. To script it, run this
in a terminal. $LIVQENO_TOKEN here is your developer login token (the
same one the CLI uses), not your project API key, because managing webhooks
is a dashboard action:
curl -X POST "$LIVQENO_API_URL/v1/projects/$PROJECT_ID/webhooks" \
-H "Authorization: Bearer $LIVQENO_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "url": "https://api.example.com/hooks", "environment": "PRODUCTION" }'Leave out events to get every event type, including new ones added
later.
Next steps
- Updating your UI? See the full payloads in RTC events, Chat events, or Stream events.
- Reacting on your backend? Read Webhooks for the payload format, signing, and retries, then build one with Handle webhooks.