livqenoDocs

ResourcesEvent catalogue

Event catalogue

Every event Livqeno sends, in one place. Checked against the code.

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:

WhereWho receives itHow manyDetails
RTC client eventsYour app, with room.on(...)17RTC events
Chat client eventsYour app, with chat.on(...)15Chat events
WebhooksYour backend, over HTTP17Below, 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

EventPayloadSent 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

EventPayloadSent 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.

EventPayloadSent 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_joined pair. One who doesn't come back is reported as DISCONNECTED, up to about 20 s after the drop, and at is 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

EventSent when
live_stream.createdA stream is created
live_stream.startedThe stream goes from CREATED to LIVE
live_stream.endedThe stream goes from LIVE to ENDED. This is final
live_stream.host_joinedA host or co-host is added
live_stream.host_leftA co-host is removed
live_stream.viewer_joinedA viewer token is created
live_stream.viewer_leftYour backend called leave() for a viewer
live_stream.broadcast_readyA broadcast-mode stream can be played from the CDN: { streamId, playbackUrl, at }. Sent once, when playback really works
live_stream.egress_failedThe video encoder for a broadcast-mode stream failed or stopped responding: { streamId, reason, at }
live_stream.recording_readyA 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_invitedA viewer was invited on stage: { streamId, identity, at }. Their client also sees a stage event
live_stream.stage_promotedA viewer was brought on stage as a co-host: { streamId, identity, role, at }
live_stream.stage_demotedA co-host was taken off stage; their publishing stops at once: { streamId, identity, at }
live_stream.restream_activeA restream destination started receiving video: { streamId, restreamId, label, at }
live_stream.restream_failedA restream destination gave up after 10 failed attempts. The stream and other destinations carry on: { streamId, restreamId, label, reason, at }
live_stream.ingress_connectedAn OBS or encoder ingest started publishing: { streamId, ingressId, protocol, at }
live_stream.ingress_disconnectedThe encoder left and didn't reconnect within the reconnect window: { streamId, ingressId, protocol, reason, at }
live_stream.ingress_failedLivqeno 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

EventPayloadSent 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

EventPayloadSent 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

EventPayloadSent 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:

Terminal
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