Event reference

Every event you can subscribe to, when it fires, and what it carries.

Video events

Events

KeyTypeSampleDescription
VIDEO_UPLOADEDoptionaleventvideo.status = "TRANSCODING"A video was registered and now exists in the library. Fires the moment POST /videos/ succeeds.
VIDEO_TRANSCODING_STARTEDoptionaleventvideo.transcoder_id = "4233029"The pipeline accepted the job. Separate from the one above because queueing can fail while the asset still exists.
VIDEO_TRANSCODING_COMPLETEDoptionaleventvideo.url = "https://…/master.m3u8"The video is playable. Carries the manifest, every rendition, duration and size.
VIDEO_TRANSCODING_FAILEDoptionaleventerror.code = "fifth-reason"Transcoding failed. Carries the pipeline’s error code and message.
VIDEO_TRANSCODING_COMPLETED fires for live stream recordings too. Check video.kind — UPLOAD or LIVESTREAM_RECORDING — before assuming someone uploaded a file.

Video payload

data.video

KeyTypeSampleDescription
idoptionalstringoI1j0SfJK3vAsset public id.
titleoptionalstringLecture 1As registered.
statusoptionalstringCOMPLETEDCOMPLETED or ERROR on the terminal events.
kindoptionalstringUPLOADUPLOAD or LIVESTREAM_RECORDING.
duration_secondsoptionalinteger | null131Reported by the pipeline.
size_bytesoptionalstring | null"5913969"Decimal string — JSON has no 64-bit integer.
urloptionalstring | nullhttps://…/master.m3u8The playable manifest — an HLS master playlist, or a DASH manifest when drm is true. Null only if the job produced nothing.
drmoptionalbooleanfalseWhether DRM was applied.
drm_content_idoptionalstring | nulld675bed3c091DRM handle, needed for a licence request.
download_urloptionalstring | nullhttps://…/original-c1742d/360p/video.mkvA single progressive file for offline use. One quality only, and a Matroska container — browsers will not play it in a <video> tag.
download_size_bytesoptionalstring | null"104858"Size of that file, as a decimal string.
download_qualityoptionalstring | null360pWhich rung the download was made from.
download_passwordoptionalstring | null"7625704"Set only when download_url is the password-protected variant — see the callout below. Null alongside a real download_url means no password is needed.
transcript_urloptionalstring | nullnullSpeech-to-text output. Null unless subtitles were requested on the upload.
summary_urloptionalstring | nullnullGenerated summary. Null unless the pipeline produced one.

data — alongside video

KeyTypeSampleDescription
renditionsoptionalarray[{ "quality": "480p", "url": "…", "download_url": null, "download_size_bytes": null, "download_password": null }]One entry per rung produced. url is the streaming manifest — HLS, or DASH under DRM, same field as data.video.url above. download_url/download_size_bytes/download_password are that rung’s own downloadable file, independent of the streaming url — an organization can enable downloads for more than one quality, so check every entry rather than only data.video.download_url, which reports just the first one found.
erroroptionalobject | null{ "code": "…", "message": "…" }Null on success. The only description of what went wrong.
storageoptionalobject | null{ "total_bytes": "503316", "hls_bytes": "398459", … }How the total splits — streaming output versus the retained source. Keys vary by job: hls/original on a normal run, drm on a DRM one.
streamoptionalobject | null{ "id": "MflonNqhUg9", "stream_key": "EXSO_1786…" }Set when the asset is a live stream recording, so you can tie it back to the broadcast. id is the stream’s public id — the same one LIVESTREAM_RECORDING_READY sends as stream.id, and the only one /livestreams/{id}/ accepts. stream_key belongs to the encoder and is not addressable.
download_url is one of two things, depending on how downloads are configured for your organization — never both on the same asset. Most organizations get a .mkv (VLC plays it; browsers and QuickTime generally do not). Organizations configured for encrypted downloads instead get a password-protected .mp4 — download_password is set alongside it, and is required to open the file. Either way, offer it as a file to save, never as a playback fallback — use url for that.

Live stream events

Events

KeyTypeSampleDescription
LIVESTREAM_SCHEDULEDoptionaleventstream.rtmp_url = "rtmp://…"A stream was created — booked for later or started right away. Carries the full push credentials, so you can act on the event alone.
LIVESTREAM_STARTEDoptionaleventstream.status = "LIVE"The broadcast began — the moment our own player first observes it actually playing, not the moment the channel was reserved.
LIVESTREAM_ENDEDoptionaleventrecording = nullThe broadcast is over. Fires from two different triggers with two different payload shapes — see the note below the table.
LIVESTREAM_RECORDING_READYoptionaleventrecording.asset_id = "MflonNqhUg9"The broadcast finished and its recording entered the library. Always fired by the pipeline, alongside LIVESTREAM_ENDED’s pipeline-triggered shape.
LIVESTREAM_ENDED carries a different stream object depending on what triggered it. Ended explicitly, through POST /livestreams/{id}/end/, it carries the same full object as LIVESTREAM_SCHEDULED and LIVESTREAM_STARTED — see the first data.stream table below. Ended by the pipeline itself — the normal path, once a broadcast simply stops — it carries the smaller object documented in the second data.stream table, alongside a recording field that is null when nothing was attached. Check which fields are actually present rather than assuming one shape.

data.stream — LIVESTREAM_SCHEDULED, LIVESTREAM_STARTED, and an explicit LIVESTREAM_ENDED

KeyTypeSampleDescription
idoptionalstringIB6HTIHnZpLThe stream’s public id — the same one /livestreams/{id}/ accepts.
public_idoptionalstringIB6HTIHnZpLSame value as id, kept for anyone already reading this field.
titleoptionalstringAlgebra IIAs set at creation.
statusoptionalstringLIVESCHEDULED, READY_TO_STREAM, LIVE, ENDED, or ERROR.
rtmp_urloptionalstringrtmp://…Complete push URL, signature included — paste into OBS as Server, leave Stream Key blank.
stream_keyoptionalstringEXSO_1788…The pipeline’s handle for this channel, not ours — not addressable through our API.
hls_urloptionalstring | nullhttps://…/master.m3u8The live playlist. Exists from creation, but 404s until the broadcaster actually connects.
playback_urloptionalstring | nullnullThe recording’s URL, once one exists. Null on every event in this table.
scheduled_atoptionalstring | null2026-09-10T09:00:00.000ZSet only on a stream booked with /livestreams/schedule/.
started_atoptionalstring | nullnullSet once LIVESTREAM_STARTED has actually fired.
ended_atoptionalstring | nullnullSet once the stream is over.
tagsoptionalobject | null{ "batch": "2026-A" }Whatever was supplied at creation.

data.stream — LIVESTREAM_ENDED (pipeline-triggered) and LIVESTREAM_RECORDING_READY

KeyTypeSampleDescription
idoptionalstringIB6HTIHnZpLThe stream’s public id — same value the table above calls id.
titleoptionalstringAlgebra IIAs set at creation.
statusoptionalstringENDEDAlways ENDED on this event.
stream_keyoptionalstringEXSO_1788…The pipeline’s handle, not ours.
duration_secondsoptionalinteger | null309Wall-clock length, from the callback.
transcoding_in_secsoptionalnumber | null309Billable transcoding time, when the callback reported it.
latencyoptionalstringNORMALNORMAL or LOW, as chosen at creation.
drmoptionalbooleanfalseWhether the recording will be DRM-protected.
scheduled_atoptionalstring | nullnullSet only if this stream was originally booked for later.
started_atoptionalstring | null2026-09-09T06:41:10.000ZWhen the broadcast actually began.
ended_atoptionalstring2026-09-09T06:46:30.328ZWhen the pipeline reported it over.

data.recording — LIVESTREAM_ENDED (pipeline-triggered) and LIVESTREAM_RECORDING_READY

KeyTypeSampleDescription
asset_idoptionalstringMflonNqhUg9The library id. This event is the only place it is announced — keep it.
transcodingoptionalbooleantrueTrue means the playable URL is still coming, on VIDEO_TRANSCODING_COMPLETED for this same asset. This event carries no URL of its own.
duration_secondsoptionalinteger | null234How long the broadcast ran.
resolutionoptionalstring | null852x480As reported by the pipeline, unparsed.
widthoptionalinteger | null852Parsed out of resolution alongside it.
heightoptionalinteger | null480Parsed out of resolution alongside it.

Ordering and duplicates

Events are not ordered. Deliveries are attempted independently, so VIDEO_TRANSCODING_STARTED can arrive after VIDEO_UPLOADED or before it — never branch on arrival order.

A single delivery can arrive more than once. Deduplicate on X-Streamos-Delivery.

typical recording sequence
LIVESTREAM_SCHEDULED          stream created, RTMP URL issued
LIVESTREAM_STARTED            broadcaster connected
   … broadcast runs …
LIVESTREAM_ENDED              pipeline-triggered shape, recording = null unless one is coming
LIVESTREAM_RECORDING_READY    asset_id issued — no URL yet, transcoding = true
   … minutes pass …
VIDEO_TRANSCODING_COMPLETED   kind = LIVESTREAM_RECORDING, url is playable