Event reference
Every event you can subscribe to, when it fires, and what it carries.
Video events
Events
| Key | Type | Sample | Description |
|---|---|---|---|
| VIDEO_UPLOADEDoptional | event | video.status = "TRANSCODING" | A video was registered and now exists in the library. Fires the moment POST /videos/ succeeds. |
| VIDEO_TRANSCODING_STARTEDoptional | event | video.transcoder_id = "4233029" | The pipeline accepted the job. Separate from the one above because queueing can fail while the asset still exists. |
| VIDEO_TRANSCODING_COMPLETEDoptional | event | video.url = "https://…/master.m3u8" | The video is playable. Carries the manifest, every rendition, duration and size. |
| VIDEO_TRANSCODING_FAILEDoptional | event | error.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
| Key | Type | Sample | Description |
|---|---|---|---|
| idoptional | string | oI1j0SfJK3v | Asset public id. |
| titleoptional | string | Lecture 1 | As registered. |
| statusoptional | string | COMPLETED | COMPLETED or ERROR on the terminal events. |
| kindoptional | string | UPLOAD | UPLOAD or LIVESTREAM_RECORDING. |
| duration_secondsoptional | integer | null | 131 | Reported by the pipeline. |
| size_bytesoptional | string | null | "5913969" | Decimal string — JSON has no 64-bit integer. |
| urloptional | string | null | https://…/master.m3u8 | The playable manifest — an HLS master playlist, or a DASH manifest when drm is true. Null only if the job produced nothing. |
| drmoptional | boolean | false | Whether DRM was applied. |
| drm_content_idoptional | string | null | d675bed3c091 | DRM handle, needed for a licence request. |
| download_urloptional | string | null | https://…/original-c1742d/360p/video.mkv | A single progressive file for offline use. One quality only, and a Matroska container — browsers will not play it in a <video> tag. |
| download_size_bytesoptional | string | null | "104858" | Size of that file, as a decimal string. |
| download_qualityoptional | string | null | 360p | Which rung the download was made from. |
| download_passwordoptional | string | 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_urloptional | string | null | null | Speech-to-text output. Null unless subtitles were requested on the upload. |
| summary_urloptional | string | null | null | Generated summary. Null unless the pipeline produced one. |
data — alongside video
| Key | Type | Sample | Description |
|---|---|---|---|
| renditionsoptional | array | [{ "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. |
| erroroptional | object | null | { "code": "…", "message": "…" } | Null on success. The only description of what went wrong. |
| storageoptional | object | 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. |
| streamoptional | object | 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
| Key | Type | Sample | Description |
|---|---|---|---|
| LIVESTREAM_SCHEDULEDoptional | event | stream.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_STARTEDoptional | event | stream.status = "LIVE" | The broadcast began — the moment our own player first observes it actually playing, not the moment the channel was reserved. |
| LIVESTREAM_ENDEDoptional | event | recording = null | The broadcast is over. Fires from two different triggers with two different payload shapes — see the note below the table. |
| LIVESTREAM_RECORDING_READYoptional | event | recording.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
| Key | Type | Sample | Description |
|---|---|---|---|
| idoptional | string | IB6HTIHnZpL | The stream’s public id — the same one /livestreams/{id}/ accepts. |
| public_idoptional | string | IB6HTIHnZpL | Same value as id, kept for anyone already reading this field. |
| titleoptional | string | Algebra II | As set at creation. |
| statusoptional | string | LIVE | SCHEDULED, READY_TO_STREAM, LIVE, ENDED, or ERROR. |
| rtmp_urloptional | string | rtmp://… | Complete push URL, signature included — paste into OBS as Server, leave Stream Key blank. |
| stream_keyoptional | string | EXSO_1788… | The pipeline’s handle for this channel, not ours — not addressable through our API. |
| hls_urloptional | string | null | https://…/master.m3u8 | The live playlist. Exists from creation, but 404s until the broadcaster actually connects. |
| playback_urloptional | string | null | null | The recording’s URL, once one exists. Null on every event in this table. |
| scheduled_atoptional | string | null | 2026-09-10T09:00:00.000Z | Set only on a stream booked with /livestreams/schedule/. |
| started_atoptional | string | null | null | Set once LIVESTREAM_STARTED has actually fired. |
| ended_atoptional | string | null | null | Set once the stream is over. |
| tagsoptional | object | null | { "batch": "2026-A" } | Whatever was supplied at creation. |
data.stream — LIVESTREAM_ENDED (pipeline-triggered) and LIVESTREAM_RECORDING_READY
| Key | Type | Sample | Description |
|---|---|---|---|
| idoptional | string | IB6HTIHnZpL | The stream’s public id — same value the table above calls id. |
| titleoptional | string | Algebra II | As set at creation. |
| statusoptional | string | ENDED | Always ENDED on this event. |
| stream_keyoptional | string | EXSO_1788… | The pipeline’s handle, not ours. |
| duration_secondsoptional | integer | null | 309 | Wall-clock length, from the callback. |
| transcoding_in_secsoptional | number | null | 309 | Billable transcoding time, when the callback reported it. |
| latencyoptional | string | NORMAL | NORMAL or LOW, as chosen at creation. |
| drmoptional | boolean | false | Whether the recording will be DRM-protected. |
| scheduled_atoptional | string | null | null | Set only if this stream was originally booked for later. |
| started_atoptional | string | null | 2026-09-09T06:41:10.000Z | When the broadcast actually began. |
| ended_atoptional | string | 2026-09-09T06:46:30.328Z | When the pipeline reported it over. |
data.recording — LIVESTREAM_ENDED (pipeline-triggered) and LIVESTREAM_RECORDING_READY
| Key | Type | Sample | Description |
|---|---|---|---|
| asset_idoptional | string | MflonNqhUg9 | The library id. This event is the only place it is announced — keep it. |
| transcodingoptional | boolean | true | True means the playable URL is still coming, on VIDEO_TRANSCODING_COMPLETED for this same asset. This event carries no URL of its own. |
| duration_secondsoptional | integer | null | 234 | How long the broadcast ran. |
| resolutionoptional | string | null | 852x480 | As reported by the pipeline, unparsed. |
| widthoptional | integer | null | 852 | Parsed out of resolution alongside it. |
| heightoptional | integer | null | 480 | Parsed 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