✅ - Fully supported ⚠️ - Partially supported 🚫 - Not supported
| Scenario | Support |
|---|---|
| VOD | ✅ |
| Live Events | ⚠️ |
| Live channels | ⚠️ |
| Server-side Ad insertion | 🚫 |
| Client-side Ad insertion | 🚫 |
session - A session means the viewing session of a piece of content.
sessionId - Refers to the unique identifier for a viewing session of a piece of content.
event - Refers to a set of data that corresponds to the viewing experience.
An analytics specification needs a reliable event flow, it is crucial that the following events are implemented correctly for the backend to be able to churn out session data.
{
event: "event_enum",
sessionId: "UID"
timestamp: 1634911668339, // UTC time. The client SHOULD send valid UTC time.
playhead: 0, // The current playhead position as an integer number of milliseconds, if the content is Live should be UTC time. -1 if unknown
duration: 0, // The duration of the content as an integer number of milliseconds. VOD = length of stream, Live = live edge in UTC. -1 if unknown
payload?: {
// Unique to each event
// If needed additional custom fields may be added here as well but the server MAY ignore them.
}
}
Some fields are NOT sent by the client. They are OPTIONAL and populated by the ingest server after it receives an event. Clients MUST NOT set them, and consumers MUST treat them as absent unless present.
domain - OPTIONAL string. The ingest server SHOULD derive this from the request's HTTP Origin header — the scheme, host, and optional port of the page that produced the event (e.g. "https://example.com"). It is server-derived and MUST NOT be trusted as a client-supplied value. When the Origin header is absent from the request, the server omits the field entirely (it is never set to an empty string or a placeholder). Because it is optional, it never appears in any event's required set, so events remain valid whether or not it is present.
{
event: "event_enum",
sessionId: "UID",
timestamp: 1634911668339,
playhead: 0,
duration: 0,
domain?: "https://example.com", // server-populated from the HTTP Origin header; omitted when the header is absent
}
playhead and duration are integer numbers of milliseconds. The JSON Schema keeps them as "type": "number" for backward compatibility with clients already in the wild, but conforming clients MUST emit them as integer milliseconds.
Clients that send sdk. A client MAY declare the SDK that produced the event with an optional top-level sdk field — sdk: { name, version }, where both name and version are required and no other keys are allowed (name is free text; the Eyevinn SDKs use web, swift, and android). A client that sends sdk MUST send playhead and duration in milliseconds, and the server trusts those values as-is and does NOT convert them.
Legacy rule for events without sdk (server SHOULD). For events that do not carry sdk, the server derives the unit from the request's HTTP Origin header: if an Origin header is present (any value, including the literal string "null"), the server multiplies playhead and duration by 1000 and rounds to the nearest integer; if no Origin header is present, the values are left unchanged. Negative sentinel values (e.g. -1, meaning "unknown") are never converted.
Known limitation. A browser client that already sends milliseconds but does NOT send sdk will be scaled ×1000 by the legacy rule above, because its requests carry an Origin header. Sending sdk is the way out: a client that declares sdk is trusted to send milliseconds and is never scaled.
Handshake. The eventsink's init response carries an epasVersion. A client sends sdk (and, for the Web SDK, milliseconds) only when the server's epasVersion is at least the version that introduces sdk. Servers older than that reject a request carrying sdk with HTTP 400.
Historical data. Rows stored before normalisation stay mixed (some seconds, some milliseconds); there is no backfill.

Sent when the client knows it should start a viewing session of content.
This SHOULD be the first event sent by the client to the server.
A restart of content SHOULD create a new session.
The server SHOULD handle events arriving out of order. Each event carries a sessionId and a timestamp for ordering in post.
MUST be sent ONCE per session.
MUST be a unique sessionId.
{
event: "init",
sessionId: "",
timestamp: -1,
playhead: -1, // if the player has an expected startTime, eg. if user continues watching a movie, use that value here.
duration: -1,
}
Contains non-critical metadata connected to the session.
Optional event that can be sent at any time. It is recommended to send it in the interval between the init and stop events.
The server SHOULD merge metadata payloads with previous metadata payloads from the session.
The server SHOULD handle live toggling from true to false (dynamic to static transitions).
{
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
live?: false,
contentTitle?: "",
},
}
customMetadataIdcustomMetadataId - OPTIONAL number. A client-defined grouping key that a client can set in the metadata payload to label its own custom metadata event types. It lets a client distinguish between several kinds of metadata events it emits (for example, one grouping key for one category of custom metadata and another for a different category), without the specification prescribing what those categories mean.
The value is opaque to the server: the client assigns it however it likes, and the server MUST NOT interpret or validate it. It is carried through the ingest server unchanged so that downstream consumers can filter by it. Being optional, it is fully back-compatible — omitting it leaves existing behaviour unchanged, and it never appears in the metadata event's required set.
The specification defines only the field and its meaning at the event level. How the id is physically persisted (the storage column, index, or query shape used to filter by it) is owned by the downstream worker/storage layer, not by the specification or the ingest server.
{
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
contentTitle?: "",
customMetadataId?: 1, // client-defined grouping key for the client's own custom metadata event types; opaque to the server
},
}
country and city)The metadata event carries two OPTIONAL server-populated fields alongside its payload. Like domain, they are NOT sent by the client — the ingest server derives them after receiving the event (typically via a geo lookup of the caller's address) and the SDKs never set them.
country - OPTIONAL string. An ISO 3166-1 alpha-2 country code (e.g. "SE"). It is server-derived and MUST NOT be trusted as a client-supplied value. When the server cannot determine the country, it omits the field entirely (it is never set to an empty string or a placeholder).
city - OPTIONAL string. The city name derived for the request. It is server-derived and MUST NOT be trusted as a client-supplied value. When the server cannot determine the city, it omits the field entirely (it is never set to an empty string or a placeholder).
Both fields sit at the top level of the metadata event (not inside payload), mirroring domain. Because they are optional, they never appear in the event's required set, so events remain valid whether or not they are present.
{
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
contentTitle?: "",
},
country?: "SE", // server-populated ISO 3166-1 alpha-2 country code; omitted when unknown
city?: "Stockholm", // server-populated city name; omitted when unknown
}
Sent on an interval, if a certain number of heartbeat events are missing the server can close a session without receiving a stopped event.
MUST be sent at a fixed interval.
The interval SHOULD be agreed upon between client and server.
{
event: "heartbeat",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload?: {
}
}
Sent when the contentUrl has been attached to the client player.
MUST be sent ONCE per session.
{
event: "loading",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
}
Sent when there is enough buffered content for the player to start playing the content.
MUST be sent ONCE per session.
{
event: "loaded",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when playback starts or resumes, when the playhead starts to move.
{
event: "playing",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when the player is paused due to a pause request.
Should not be sent when playback stops for other reasons, e.g. buffering or seeking.
{
event: "paused",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when the player starts buffering. Buffering that happens when seeking or loading should be ignored.
Note, since it is possible to pause and unpause during buffering, the corresponding playing should not trigger until after buffered, since that is when the playhead can start moving again.
{
event: "buffering",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when the player has finished buffering.
If the buffering is interrupted by seeking, buffered SHOULD still be sent (before the seeking event) to close the buffering period.
If the buffering is interrupted by stopped, buffered should NOT be sent.
{
event: "buffered",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0
}
Sent when the player starts seeking to a new playhead time.
The playhead MUST be the current playhead time NOT the target playhead time.
Note, since it is possible to pause and unpause during seeking, the corresponding playing should not trigger until after seeked, since that is when the playhead can start moving again.
MUST not be sent during loading.
{
event: "seeking",
sessionId: "",
timestamp: 0,
playhead: 0, // the current playhead
duration: 0
}
Sent when the player has finished seeking to the new playhead time and is ready to start playing.
playhead MUST be the new playhead time.
{
event: "seeked",
sessionId: "",
timestamp: 0, // the new playhead
playhead: 0,
duration: 0
}
Can be sent after loaded event, before the stopped event.
Sent when the player successfully switches to a new bitrate.
{
event: "bitrate_changed",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
bitrate: 0, // bitrate in Kbps
width?: 0, // video width in pixels
height?: 0, // video height in pixels
videoBitrate?: 0, // if available provide the bitrate for the video track
audioBitrate?: 0, // if available provide the bitrate for the audio track
}
}
Sent when playback stops.
Can be sent after init.
{
event: "stopped",
sessionId: string,
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
reason: "", // eg. "ended", "aborted", "error"
}
}
Sent when a fatal error occurs.
The following stopped event SHOULD be sent with reason: "error".
{
event: "error",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
category?: "", // eg. NETWORK, DECODER, etc.
code: "",
message?: "",
data?: {}
}
}
Sent when a non-fatal error occurs.
A playback error that the player can recover from without interruption.
{
event: "warning",
sessionId: "",
timestamp: 0,
playhead: 0,
duration: 0,
payload: {
category?: "", // eg. NETWORK, DECODER, osv.
code "",
message?: "",
data?: {}
}
}
Generated using TypeDoc