Livqeno Docs

Stream Events

The seven live-stream webhooks, their payloads, and the client events that are not webhooks.

Live streaming emits seven webhook event types. They are the only Livqeno webhooks that are not chat events.

The seven

EventFires when
live_stream.createdA stream is created, before it is live
live_stream.startedstart() moved it CREATED → LIVE
live_stream.endedend() moved it LIVE → ENDED — terminal
live_stream.host_joinedA host or co-host was registered and credentials minted
live_stream.host_leftA co-host was removed
live_stream.viewer_joinedA viewer token was minted
live_stream.viewer_leftYour backend called leave() for a viewer

Subscribe to specific types, or leave the list empty to receive all of them:

curl -X POST "$RAVEN_API_URL/v1/projects/$PROJECT_ID/webhooks" \
  -H "Authorization: Bearer $RAVEN_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "url": "https://api.example.com/hooks/raven",
        "environment": "PRODUCTION",
        "events": ["live_stream.started", "live_stream.ended"]
      }'

Payloads

Each arrives in the standard webhook envelope, with the stream's identity in data. The fields differ per event — these are the exact keys each one carries:

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

Note that title is on created only. If you need it on the others, keep it from created or read it back 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"
  }
}

Treat data as extensible: fields may be added, so ignore ones you do not recognise rather than failing.

viewer_left depends on you

live_stream.viewer_joined fires when your backend mints a viewer token. live_stream.viewer_left fires when your backend calls:

await raven.liveStreams.leave(stream.id, 'user-99');

Livqeno does not infer it from the media connection dropping. If you never call leave(), the event never fires and your own analytics will show viewers who joined and never left. Wire it into whatever your product treats as "closed the page".

What is not a webhook

Everything happening inside the stream is a client event, on the underlying room and conversation:

You wantUse
A viewer's video arrivedstream.room.on('trackSubscribed', …)
Someone joined the roomstream.room.on('participantJoined', …)
A live chat messagestream.chat.on('message', …)
A reactionstream.chat.on('reactionAdded', …)
Connection droppedstream.room.on('connectionStateChanged', …)

stream.room is an ordinary Room and stream.chat an ordinary ChatClient, so RTC events and Chat events apply unchanged. There is no stream-specific event emitter to learn.

Verify and dedupe

Same rules as every webhook: verify the signature, dedupe on Raven-Event-Id, order by createdAt, respond 2xx within 5 seconds. See Handle webhooks.

One case worth thinking about: live_stream.ended is terminal, but a retry means you may receive it twice. Make your finalisation idempotent.

Next steps