livqenoDocs

Live StreamingStream Events

Stream Events

The live-stream webhooks, what they contain, and the client events that aren't webhooks.

Your backend often needs to react when something happens in a stream: post "We're live!" when it starts, keep a list of who attended, or close the event page when it ends. Livqeno tells your backend with webhooks.

Where this fits: a webhook is an HTTP POST that Livqeno sends to a URL on your backend. It works the same for every Livqeno product. If you haven't set one up yet, start with Webhooks. This page lists the live-stream events and what's in them.

Things that happen inside the app, like a new chat message on screen, come as client events instead. See Client events.

The seven events

EventSent when
live_stream.createdA stream is created (not live yet)
live_stream.startedstart() moved it from CREATED to LIVE
live_stream.endedend() moved it from LIVE to ENDED (final)
live_stream.host_joinedA host or co-host was added and got credentials
live_stream.host_leftA co-host was removed
live_stream.viewer_joinedA viewer token was made
live_stream.viewer_leftYour backend called leave() for a viewer

Two more events exist for streams using the broadcast delivery mode: live_stream.broadcast_ready and live_stream.egress_failed. See the Event catalogue.

Restream and encoder ingest add five more:

EventSent whendata fields
live_stream.restream_activeA restream destination started receiving videostreamId, restreamId, label, at
live_stream.restream_failedA restream destination reached FAILEDstreamId, restreamId, label, reason, at
live_stream.ingress_connectedAn OBS or encoder ingest started publishingstreamId, ingressId, protocol, at
live_stream.ingress_disconnectedThe encoder left and didn't come back within the reconnect windowstreamId, ingressId, protocol, reason, at
live_stream.ingress_failedLivqeno refused the encoder's mediastreamId, ingressId, protocol, reason, at

Note that host_joined and viewer_joined fire when your backend makes the credential, not when the person actually connects.

Subscribe

Register a webhook endpoint once per environment, from your terminal or a setup script. List the events you want. Leave the list empty to get all of them:

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/livqeno",
        "environment": "PRODUCTION",
        "events": ["live_stream.started", "live_stream.ended"]
      }'

Payloads

Each event uses the standard webhook envelope. The stream details are in data. These are the exact fields:

Eventdata fields
live_stream.createdstreamId, title, visibility, hostIdentity, createdAt
live_stream.startedstreamId, startedAt
live_stream.endedstreamId, endedAt, durationMs (null if it never started)
live_stream.host_joinedstreamId, identity, role, at
live_stream.host_leftstreamId, identity, at
live_stream.viewer_joinedstreamId, identity, at
live_stream.viewer_leftstreamId, identity, at

title is only on created. Need it later? Save it then, or read it with liveStreams.get().

{
  "id": "evt_GSyoZrH7qF3tZms0QsW4Gw",
  "type": "live_stream.created",
  "projectId": "3e48ccb1-...",
  "environment": "PRODUCTION",
  "createdAt": "2026-09-08T12:00:00.000Z",
  "data": {
    "streamId": "stream_jRoD1T3EXh0PMJRGG4zYzQ",
    "title": "Friday Q&A",
    "visibility": "PUBLIC",
    "hostIdentity": "user-1",
    "createdAt": "2026-09-08T12:00:00.000Z"
  }
}

New fields may be added to data. Ignore fields you don't know instead of failing.

viewer_left only fires if you call leave()

  • viewer_joined fires when your backend makes a viewer token.
  • viewer_left fires only when your backend calls:
await livqeno.liveStreams.leave(stream.id, 'user-99');

Client events (not webhooks)

What happens inside the stream comes as client events in your app, on the room and the chat. Use these to update the screen:

You want to know when…Use
A video arrivedstream.room.on('trackSubscribed', …)
Someone joined the roomstream.room.on('participantJoined', …)
A chat message arrivedstream.chat.on('message', …)
A reaction arrivedstream.chat.on('reactionAdded', …)
The connection droppedstream.room.on('connectionStateChanged', …)

stream.room is a normal Room and stream.chat is a normal ChatClient. So RTC events and Chat events work the same. There's nothing new to learn.

Verify and remove duplicates

If your endpoint is slow or down, Livqeno sends the event again later. So follow the same rules as every webhook:

  1. Check the signature.
  2. Skip duplicates using Livqeno-Event-Id.
  3. Order by createdAt.
  4. Reply with a 2xx within 5 seconds.

See Handle webhooks.

live_stream.ended can arrive twice if it's retried. Make sure handling it twice is safe.

Next steps