LiveStreamEndedPayload live.stream.ended@1
Generated at from
openvibe-contracts v0.33.0 and
openvibe-sdk v0.5.0.
- Version
- 1.0.0
- Owner
- live
- Visibility
- public
- Status
- active
- Compatibility
- backward
- Decision
- ADR-004
- Schema
https://openvibe.network/contracts/events/payloads/live.stream.ended.v1.json
live.stream.ended v1 (OpenVibe.Live server/events/stream-events.js envelopeFor, fired by server/db/database.js endStream). A live streams row ended (the streamer stopped, ingest dropped, a newer session replaced it on the same slot, or an admin ended it). Ending a row that is not live emits nothing, so each stream ends once. Written to Live's event_outbox in the transaction that ends the row. Everything live.stream.started carries, as it is at the end (the title and category may have changed), plus ended_at and duration_seconds. Carries public channel facts only (the stream is listed publicly already); consumers such as Network's go-live notifications decide who hears about it. Never the stream key, the Live user id or the description. Envelope: subject { type: stream, id: <stream_id as a string>, revision: 2 }, visibility public, priority important, actor the streamer's user subject when Live knows it, else service:live.
Fields
| Field | Type | Required | Description | Constraints |
|---|---|---|---|---|
stream_id | integer | yes | Live's streams row id (the envelope subject id). |
|
channel | object | yes |
| |
channel.username | string | yes | The streamer's Live username (the channel slug). |
|
channel.display_name | string | yes | The display name, or the username when there is none. |
|
channel.url | string | yes | The channel page, https://openvibe.live/@<username, URI-encoded>. Events from Live before 4c70332 (2026-09-23) carry https://openvibe.live/<username>, which Live answers with a 301 to the @ address since that commit. |
|
channel.subject | object | The streamer's canonical subject; absent until Live has seen it (a Network sign-in or the subject backfill). |
| |
channel.subject.type | const | yes |
| |
channel.subject.id | string | yes |
| |
title | string | yes | ||
category | string | null | yes | null when the streamer chose none (the AI classifies the stream from what it shows). | |
protocol | enum | yes | How the stream is ingested (WHIP is webrtc; OpenRe and RTMP are rtmp). |
|
is_nsfw | boolean | yes | ||
started_at | string | null | yes | UTC. null only for a row without a start time, which createStream never writes. |
|
ended_at | string | yes | UTC. |
|
duration_seconds | integer | null | yes | Whole seconds from started_at to ended_at; null when the row has no start time. |
|
Examples
From the contract's own test fixtures: valid ones validate, rejected ones must fail.
Valid: ended
{
"stream_id": 4211,
"channel": {
"username": "alice",
"display_name": "Alice",
"url": "https://openvibe.live/@alice",
"subject": {
"type": "user",
"id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
}
},
"title": "Morning walk through Shinjuku",
"category": "irl",
"protocol": "webrtc",
"is_nsfw": false,
"started_at": "2026-09-23T18:02:11.000Z",
"ended_at": "2026-09-23T19:02:41.000Z",
"duration_seconds": 3630
}Valid: no-start-time
{
"stream_id": 4211,
"channel": {
"username": "alice",
"display_name": "Alice",
"url": "https://openvibe.live/@alice",
"subject": {
"type": "user",
"id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
}
},
"title": "Morning walk through Shinjuku",
"category": "irl",
"protocol": "webrtc",
"is_nsfw": false,
"started_at": null,
"ended_at": "2026-09-23T19:02:41.000Z",
"duration_seconds": null
}Rejected: subject-not-a-user
{
"stream_id": 4211,
"channel": {
"username": "alice",
"display_name": "Alice",
"url": "https://openvibe.live/@alice",
"subject": {
"type": "service",
"id": "live"
}
},
"title": "Morning walk through Shinjuku",
"category": "irl",
"protocol": "webrtc",
"is_nsfw": false,
"started_at": "2026-09-23T18:02:11.000Z",
"ended_at": "2026-09-23T19:02:41.000Z",
"duration_seconds": 3630
}Rejected: without-ended-at
{
"stream_id": 4211,
"channel": {
"username": "alice",
"display_name": "Alice",
"url": "https://openvibe.live/@alice",
"subject": {
"type": "user",
"id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
}
},
"title": "Morning walk through Shinjuku",
"category": "irl",
"protocol": "webrtc",
"is_nsfw": false,
"started_at": "2026-09-23T18:02:11.000Z",
"duration_seconds": 3630
}Validate
const contracts = require('openvibe-contracts');
contracts.validate('live.stream.ended@1', value); // { valid, errors: [{ path, message }] }