PRIVATE ALPHA

Runtime webhook module

Configure outbound webhook delivery and consume every emitted event with its exact payload fields.

Event emission

Every event below requires webhook.enabled=true, a valid delivery configuration and the listed feature or lifecycle condition. Emission is a local handoff to bounded delivery, not a guarantee that the receiver gets the event.

EventAdditional emission conditionEmitterNotes
stream.startedAn enabled push listener accepts a local logical stream, or a managed pull creates one.Push listener or managed pull controllerTransport reconnects of a managed pull do not create another start.
stream.stoppedThe corresponding local push or managed-pull lifecycle reaches its terminal path.Push listener or managed pull controllerCarries a closed reason. In legacy mode, repair handover also emits a stop/start pair.
stream.updatedwebhook.streamState.enabled=true and the collector commits a changed summary for an admitted local push or managed pull.Stream-state collectorA complete replacement, not a delta; changes may be coalesced. No event when the opt-in is disabled.
recording.startedEnabled recording becomes locally available after its first durable multivariant write.Recording availability callbackNot emitted just because recording was configured or a session was opened; an empty session emits none.
recording.stoppedAn available recording reaches durable local close with its renditions finalized.Recording close callbackNot emitted for terminally failed, degraded or incompletely finalized recordings.
recording.publishedEnabled S3 publication completes the durable recording dependency tree and this process writes the publication marker.S3 publisher callbackIncludes recovery completed by this process; reopening an already published session emits nothing.
recording.failedThe recording session becomes terminally unavailable and its availability is withdrawn or permanently prevented.Recording terminal-failure callbackA single failed rendition does not emit this event while another rendition keeps the recording available.
thumbnail.availablewebhook.thumbnailAvailable.enabled=true and enabled local thumbnail output durably commits a JPEG and a valid current descriptor.Thumbnail commit callbackStale or duplicate callbacks cannot emit availability. This event does not assert S3 publication.

Stream-state observation covers locally admitted push and managed-pull streams, not cascade input. With stream-state enabled, repair handover preserves the logical instance and produces state updates instead of an intermediate stop/start pair.

Delivery configuration

Each delivery is an HTTP POST to webhook.url with one JSON event and Content-Type: application/json. An optional token is sent as Authorization: Bearer <token>.

Field under webhookDefaultPurpose
enabledfalseEnables outbound delivery. Disabled configuration starts no dispatcher or outbound requests.
urlrequired when enabledDeployment-supplied receiver URL.
authTokenemptyOptional bearer token.
requestTimeout5sUpper bound for each HTTP attempt.
maxAttempts5Maximum attempts for an event.
queueSize256Lifecycle and recording queue capacity.
maxConcurrentDeliveries4Lifecycle and recording delivery concurrency.
shutdownFlushTimeout2sBounded queue flush during shutdown.
streamState.enabledfalseExtends stream start/stop and enables stream.updated.
thumbnailAvailable.enabledfalseEnables thumbnail.available independently of stream state.

The state-update and thumbnail dispatchers each have a separate capacity of 64 queued or in-flight events and one HTTP worker. Recording payloads are unchanged by either opt-in; thumbnail payloads do not acquire stream-state fields.

Delivery is best effort with retries and possible duplicates. Success is any 2xx response; network failures, timeouts, 408, 429 and 5xx responses can retry. Other responses are final drops; redirects are not followed. Lifecycle and recording delivery preserves per-stream order, but the separate state and thumbnail lanes have no cross-lane ordering guarantee.

The queue is in memory. Queue saturation, exhausted attempts, shutdown or a process crash can lose events; there is no durable outbox or replay. Consumers that need durable truth must reconcile with the API or object storage.

Common payload and conditional fields

Every event contains the four common fields below. Timestamps use UTC RFC 3339 with exactly millisecond precision, for example 2026-08-18T10:12:13.123Z. Additional fields follow the matrix; absent fields are omitted, not empty strings or null. Nullable fields inside summary follow the separate rules below.

FieldJSON typeMeaning
eventTypestringOne of the eight event names on this page.
occurredAtstringEvent time, retained unchanged across retries.
streamIdstringCanonical stream identifier.
createdTimestringLogical stream admission time; recovery publication uses the time parsed from the reserved session path.
recordingUrlstringOnly recording.started, recording.stopped and recording.published: the reserved session's root-relative VOD entry path.
reasonstringOnly stream.stopped and recording.failed: a value from the respective closed vocabulary below.
schemaVersionintegerOnly opted-in stream events; currently 1.
instanceIdstringOnly opted-in stream events: 32 lowercase hexadecimal epoch characters, a dot and a decimal instance counter.
revisionstringOnly opted-in stream events: a positive decimal revision. Preserve it as a string.
summaryobjectOnly opted-in stream.started and stream.updated; a complete state replacement.
thumbnailobjectOnly thumbnail.available; the complete descriptor described below.
EventCommon fieldsrecordingUrlreasonAdditional fields
stream.startedAll fourAbsentAbsentschemaVersion, instanceId, revision, summary when streamState is enabled
stream.updatedAll fourAbsentAbsentschemaVersion, instanceId, revision, summary (requires streamState)
stream.stoppedAll fourAbsentPresentschemaVersion, instanceId, revision when streamState is enabled; never summary
recording.startedAll fourPresentAbsentNone
recording.stoppedAll fourPresentAbsentNone
recording.publishedAll fourPresentAbsentNone
recording.failedAll fourAbsentPresentNone
thumbnail.availableAll fourAbsentAbsentthumbnail only (requires thumbnailAvailable)

Legacy mode: when stream-state mode is off, only stream.started and stream.stopped retain the legacy stream payloads without state fields. stream.updated is not emitted.

Treat recordingUrl as an opaque path. Never build it from createdTime: a reserved recording path can have a collision-adjusted timestamp. Supply the VOD scheme and host from deployment configuration; the origin HTTP listener does not serve /vod/. Recovery publication has no original admission timestamp, so its createdTime may reflect that adjusted path time.

Stream-state summary contract

summary is a full replacement. A missing optional field or an explicit null clears that part of the previous state. It never includes thumbnail state.

Field within summaryPresence and value
stateRequired string: starting, waitingForKeyframe, healthy, degraded, reconnecting, stopping or stopped.
sourceRequired object with kind (push or pull) and transportState. transportState is always present: null for push; for pull, null or dialing, connected, backoff, failed, stopping.
healthRequired object or null before evaluation. An object always contains status, component and reason. Status is healthy, degraded or unavailable; a healthy example is {"status":"healthy","component":"lifecycle","reason":"none"}, not status "ok".
input, outputBoth keys are required, null before discovery/composition; otherwise each is an object with count (integer), truncated (boolean, including false), and tracks (array, at most 16 entries in PMT order). count is the full track count.
input.tracks[], output.tracks[]Each object requires kind and codec strings. language, width, height, sampleRate and layer are omitted when empty or zero; language is at most 16 ASCII bytes. Numeric fields describe declared metadata.
transcodeRequired, null while inactive; otherwise an object with profile and reason strings plus video and audio booleans, including false.
availabilityRequired object with all five booleans, including false: liveHls, recording, cascadeSrt, stop, removePull.
truncatedOptional top-level summary boolean: omitted when false, true only for the oversized-start fallback. This differs from the required input/output truncated fields.

For local stream health, component is lifecycle, source, media or output. Its reason is none, starting, source_connecting, source_reconnecting, source_unreachable, source_no_media, waiting_for_keyframe, missing_idr, media_degraded, output_unavailable, stopping or internal_error, as applicable to the evaluated state.

The escaped JSON body is bounded to 16 KiB. An oversized start retains its identity but uses state: "starting", the known source kind, null transport/health/input/output/transcode fields, all availability flags false and summary.truncated: true. An oversized update is dropped. Stop never carries a summary.

Use (instanceId, revision, eventType) for retry identity. Updates can arrive before start or after stop. Ignore older revisions; retain a terminal tombstone after stop so a delayed update cannot revive the instance. Revisions can skip because updates coalesce. A new process epoch requires explicit resynchronization.

Thumbnail descriptor contract

The thumbnail object has no optional fields. It contains schemaVersion (integer, currently 1), streamId, instanceId, revision, sessionId, fileName, path, contentType, width, height, pts90k and availableAt.

All are strings except schemaVersion, width and height, which are integers. revision is a positive decimal string in the thumbnail domain, independent of stream-state revision; pts90k is a signed decimal string. contentType is image/jpeg, dimensions are positive, and availableAt uses the common UTC millisecond timestamp format.

path is /thumbnails/{sessionId}/{fileName}, without scheme or host. The descriptor is committed only after local JPEG and directory durability and current generation/session checks. Its envelope is also bounded to 16 KiB. It carries no top-level schemaVersion, instanceId, revision or summary.

Terminal reason values

stream.stopped: transport_disconnect, transport_error, peer_idle_timeout, media_timeout, invalid_input, resource_limit, event_queue_overflow, worker_panic, shutdown, internal_error, api_stop or managed-pull retry_deadline. Legacy repair handover also emits repair_hand_over; stream-state mode suppresses that intermediate stop.

recording.failed: queue_full, session_cap, low_water, disk_full, write_timeout, write_error, permission_denied, invalid_config or rendition_rejected. These are closed reason codes, never raw errors.

stream.started

With webhook.enabled=true, an enabled push listener accepting a local logical stream or a managed pull creating one emits this event. Managed-pull transport reconnects do not create another start.

This is the legacy start payload. With webhook.streamState.enabled=true, it also contains schemaVersion, instanceId, revision and the complete summary described above.

{
  "eventType": "stream.started",
  "occurredAt": "2026-08-18T10:12:13.123Z",
  "streamId": "tenant:channel-123",
  "createdTime": "2026-08-18T10:12:12.900Z"
}

stream.updated

With both webhook.enabled=true and webhook.streamState.enabled=true, the collector emits this event when it commits a changed summary for an admitted local push or managed pull. The payload is a complete replacement; changes may be coalesced.

{
  "eventType": "stream.updated",
  "occurredAt": "2026-08-18T10:20:00.321Z",
  "streamId": "tenant:channel-123",
  "createdTime": "2026-08-18T10:12:12.900Z",
  "schemaVersion": 1,
  "instanceId": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6.42",
  "revision": "5",
  "summary": {
    "state": "healthy",
    "source": {
      "kind": "push",
      "transportState": null
    },
    "health": {
      "status": "healthy",
      "component": "lifecycle",
      "reason": "none"
    },
    "input": {
      "count": 1,
      "truncated": false,
      "tracks": [
        {
          "kind": "video",
          "codec": "h264",
          "width": 1920,
          "height": 1080
        }
      ]
    },
    "output": {
      "count": 1,
      "truncated": false,
      "tracks": [
        {
          "kind": "video",
          "codec": "h264",
          "width": 1920,
          "height": 1080
        }
      ]
    },
    "transcode": null,
    "availability": {
      "liveHls": true,
      "recording": true,
      "cascadeSrt": false,
      "stop": true,
      "removePull": false
    }
  }
}

stream.stopped

With webhook.enabled=true, this event reports the terminal path of the corresponding local push or managed-pull lifecycle. Legacy repair handover also emits a stop/start pair; stream-state mode suppresses that intermediate stop.

This is the legacy stop payload. Stream-state opt-in adds schemaVersion, instanceId and revision, but never summary.

{
  "eventType": "stream.stopped",
  "occurredAt": "2026-08-18T10:31:02.481Z",
  "streamId": "tenant:channel-123",
  "createdTime": "2026-08-18T10:12:12.900Z",
  "reason": "media_timeout"
}

recording.started

With webhook.enabled=true and recording enabled, this event is emitted when the recording first becomes locally available after its first durable multivariant write. Opening an empty recording session does not emit it.

{
  "eventType": "recording.started",
  "occurredAt": "2026-08-18T10:12:19.004Z",
  "streamId": "tenant:channel-123",
  "createdTime": "2026-08-18T10:12:12.900Z",
  "recordingUrl": "/vod/2026/08/18/tenant%3Achannel-123/101212900/index.m3u8"
}

recording.stopped

With webhook.enabled=true, an available recording emits this event on durable local close with its renditions finalized. A terminally failed, degraded or incompletely finalized recording does not emit it.

{
  "eventType": "recording.stopped",
  "occurredAt": "2026-08-18T10:30:12.500Z",
  "streamId": "tenant:channel-123",
  "createdTime": "2026-08-18T10:12:12.900Z",
  "recordingUrl": "/vod/2026/08/18/tenant%3Achannel-123/101212900/index.m3u8"
}

recording.published

With webhook.enabled=true and S3 publication enabled, this event is emitted when this process completes the durable recording dependency tree and writes the publication marker. Recovery completed by this process can emit it; reopening an already published session cannot.

{
  "eventType": "recording.published",
  "occurredAt": "2026-08-18T10:33:42.501Z",
  "streamId": "tenant:channel-123",
  "createdTime": "2026-08-18T10:12:12.900Z",
  "recordingUrl": "/vod/2026/08/18/tenant%3Achannel-123/101212900/index.m3u8"
}

recording.failed

With webhook.enabled=true, this event is emitted when a recording becomes terminally unavailable and its availability is withdrawn or permanently prevented. A single failed rendition does not emit it while another keeps the recording available. This example is a separate failed recording outcome, not a continuation of the successfully published example.

{
  "eventType": "recording.failed",
  "occurredAt": "2026-08-18T10:14:31.900Z",
  "streamId": "tenant:channel-123",
  "createdTime": "2026-08-18T10:12:12.900Z",
  "reason": "session_cap"
}

thumbnail.available

With both webhook.enabled=true and webhook.thumbnailAvailable.enabled=true, enabled local thumbnail output emits this event after durably committing a JPEG and a valid current descriptor. Stale or duplicate callbacks cannot emit it; the event does not assert S3 publication.

{
  "eventType": "thumbnail.available",
  "occurredAt": "2026-08-30T12:00:00.000Z",
  "streamId": "demo",
  "createdTime": "2026-08-30T12:00:00.000Z",
  "thumbnail": {
    "schemaVersion": 1,
    "streamId": "demo",
    "instanceId": "aabbccddeeff00112233445566778899.1",
    "revision": "3",
    "sessionId": "2026/08/30/demo/120000000",
    "fileName": "000000000001-00000000000000090000.jpg",
    "path": "/thumbnails/2026/08/30/demo/120000000/000000000001-00000000000000090000.jpg",
    "contentType": "image/jpeg",
    "width": 400,
    "height": 225,
    "pts90k": "90000",
    "availableAt": "2026-08-30T12:00:00.000Z"
  }
}

Configuration reference

The configuration document rules apply to every runtime module. The complete field reference covers every accepted option; the maximal composition shows how modules fit together.