Playback & Engagement Events
Session, milestone, completion rate, and player error events.
These events are generated as Moviie processes playback heartbeats and daily metric thresholds. They reflect viewer behaviour after a video is published. For the envelope format, shared headers, and retry policy, see Outbound Webhooks.
Playback activity events are not emitted synchronously with the viewer action. They are delivered after Moviie processes stored heartbeats, so a short delay is expected.
Examples reuse the following placeholders throughout this page:
organization_id → 11111111-1111-1111-1111-111111111111,
video id → 00000000-0000-4000-8000-00000000aa01,
session id → 33333333-3333-4333-8333-333333333333.
playback.session.ended
When it fires. Moviie processes a completed playback session. Watch time and completion are derived from heartbeats and video duration.
{
"id": "evt_e3f4a5b6-c7d8-4901-e234-567890123456",
"type": "playback.session.ended",
"created_at": "2026-05-09T22:00:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": {
"id": "00000000-0000-4000-8000-00000000aa01",
"external_id": "ext_video_abc123",
"status": "complete",
"title": "My video.mp4",
"duration_ms": 125400,
"width": 1920,
"height": 1080,
"collection_id": "22222222-2222-4222-8222-222222222222"
},
"session": {
"id": "33333333-3333-4333-8333-333333333333",
"watch_time_seconds": 87,
"completion_percent": 69,
"country": "BR",
"device_type": "desktop",
"referrer": "https://example.com/page"
}
}
}| Path | Type | Description |
|---|---|---|
data.video | object | See shared data.video. |
data.session.id | string | Playback session UUID. |
data.session.watch_time_seconds | number | Estimated watch time: heartbeat interval × count. |
data.session.completion_percent | number | 0-100; max position reached divided by duration. |
data.session.country | string | Optional; ISO country code derived from session geo. |
data.session.device_type | string | Optional; coarse device class (for example desktop, mobile). |
data.session.referrer | string | Optional; referrer URL captured when the session started. |
playback.cta.clicked
When it fires. The player reports a CTA click with a cta_id attached.
{
"id": "evt_f4a5b6c7-d8e9-4012-f345-678901234567",
"type": "playback.cta.clicked",
"created_at": "2026-05-09T22:01:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": {
"id": "00000000-0000-4000-8000-00000000aa01",
"external_id": "ext_video_abc123",
"status": "complete",
"title": "My video.mp4",
"duration_ms": 125400,
"width": 1920,
"height": 1080,
"collection_id": "22222222-2222-4222-8222-222222222222"
},
"session": { "id": "33333333-3333-4333-8333-333333333333" },
"cta": {
"id": "66666666-6666-4666-8666-666666666666",
"position_seconds": 45
}
}
}| Path | Type | Description |
|---|---|---|
data.video | object | See shared data.video. |
data.session.id | string | Session UUID. |
data.cta.id | string | CTA UUID. |
data.cta.position_seconds | number | Optional; player position (in seconds) when the CTA was clicked, if reported. |
playback.milestone.25
When it fires. A heartbeat shows the viewer reached ≥ 25% of the video's duration in that session. Fires at most once per session per milestone (deduplication via the milestone tracking table).
{
"id": "evt_a5b6c7d8-e9f0-4123-a456-789012345678",
"type": "playback.milestone.25",
"created_at": "2026-05-09T22:02:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": {
"id": "00000000-0000-4000-8000-00000000aa01",
"external_id": "ext_video_abc123",
"status": "complete",
"title": "My video.mp4",
"duration_ms": 125400,
"width": 1920,
"height": 1080,
"collection_id": "22222222-2222-4222-8222-222222222222"
},
"session": { "id": "33333333-3333-4333-8333-333333333333" },
"milestone": 25
}
}| Path | Type | Description |
|---|---|---|
data.video | object | See shared data.video. |
data.session.id | string | Session UUID. |
data.milestone | number | Always 25 for this event type. |
playback.milestone.50
When it fires. Same conditions as playback.milestone.25, for the 50% threshold.
{
"id": "evt_b6c7d8e9-f0a1-4234-b567-890123456789",
"type": "playback.milestone.50",
"created_at": "2026-05-09T22:03:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": {
"id": "00000000-0000-4000-8000-00000000aa01",
"external_id": "ext_video_abc123",
"status": "complete",
"title": "My video.mp4",
"duration_ms": 125400,
"width": 1920,
"height": 1080,
"collection_id": "22222222-2222-4222-8222-222222222222"
},
"session": { "id": "33333333-3333-4333-8333-333333333333" },
"milestone": 50
}
}| Path | Type | Description |
|---|---|---|
data.video | object | See shared data.video. |
data.session.id | string | Session UUID. |
data.milestone | number | Always 50 for this event type. |
playback.milestone.75
When it fires. Same conditions, for the 75% threshold.
{
"id": "evt_c7d8e9f0-a1b2-4345-c678-901234567890",
"type": "playback.milestone.75",
"created_at": "2026-05-09T22:04:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": {
"id": "00000000-0000-4000-8000-00000000aa01",
"external_id": "ext_video_abc123",
"status": "complete",
"title": "My video.mp4",
"duration_ms": 125400,
"width": 1920,
"height": 1080,
"collection_id": "22222222-2222-4222-8222-222222222222"
},
"session": { "id": "33333333-3333-4333-8333-333333333333" },
"milestone": 75
}
}| Path | Type | Description |
|---|---|---|
data.video | object | See shared data.video. |
data.session.id | string | Session UUID. |
data.milestone | number | Always 75 for this event type. |
playback.milestone.100
When it fires. Same conditions, for the 100% threshold: the session reached (or exceeded) the full video duration.
{
"id": "evt_d8e9f0a1-b2c3-4456-d789-012345678901",
"type": "playback.milestone.100",
"created_at": "2026-05-09T22:05:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": {
"id": "00000000-0000-4000-8000-00000000aa01",
"external_id": "ext_video_abc123",
"status": "complete",
"title": "My video.mp4",
"duration_ms": 125400,
"width": 1920,
"height": 1080,
"collection_id": "22222222-2222-4222-8222-222222222222"
},
"session": { "id": "33333333-3333-4333-8333-333333333333" },
"milestone": 100
}
}| Path | Type | Description |
|---|---|---|
data.video | object | See shared data.video. |
data.session.id | string | Session UUID. |
data.milestone | number | Always 100 for this event type. |
video.view.milestone
When it fires. Cumulative daily views for a video cross
100, 1 000, or 10 000 for the first time. Only video.id is sent under data: the full data.video snapshot is not included.
{
"id": "evt_e9f0a1b2-c3d4-4567-e890-123456789012",
"type": "video.view.milestone",
"created_at": "2026-05-10T08:00:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": { "id": "00000000-0000-4000-8000-00000000aa01" },
"milestone_views": 1000
}
}| Path | Type | Description |
|---|---|---|
data.video.id | string | Video UUID. No other video fields are sent. |
data.milestone_views | number | One of 100, 1000, 10000. |
video.completion_rate.threshold
When it fires. While updating daily metrics, the average completion rate (watch time ÷ plays ÷ duration) crosses 40% or 50% from below to at-or-above. Fires once per threshold per direction per day.
{
"id": "evt_f0a1b2c3-d4e5-4678-f901-234567890123",
"type": "video.completion_rate.threshold",
"created_at": "2026-05-10T08:01:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": { "id": "00000000-0000-4000-8000-00000000aa01" },
"completion_rate_percent": 45,
"threshold_percent": 40,
"date": "2026-05-10"
}
}| Path | Type | Description |
|---|---|---|
data.video.id | string | Video UUID. No other video fields are sent. |
data.completion_rate_percent | number | Rounded completion rate after the metric update. |
data.threshold_percent | number | Config threshold crossed: 40 or 50. |
data.date | string | Aggregate calendar day (YYYY-MM-DD). |
playback.error
When it fires. The player reports a playback error for a session. The error code is
forwarded as-is from the playback payload; unknown is used as a fallback if the code is
missing.
{
"id": "evt_a1b2c3d4-e5f6-4789-a012-3456789abcde",
"type": "playback.error",
"created_at": "2026-05-09T22:10:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": {
"id": "00000000-0000-4000-8000-00000000aa01",
"external_id": "ext_video_abc123",
"status": "complete",
"title": "My video.mp4",
"duration_ms": 125400,
"width": 1920,
"height": 1080,
"collection_id": "22222222-2222-4222-8222-222222222222"
},
"session": { "id": "33333333-3333-4333-8333-333333333333" },
"error": { "code": "network_failed" }
}
}| Path | Type | Description |
|---|---|---|
data.video | object | See shared data.video. |
data.session.id | string | Session UUID. |
data.error.code | string | Error code from the playback event; unknown if absent. |