Documentation Index
Fetch the complete documentation index at: https://docs.permutive.com/llms.txt
Use this file to discover all available pages before exploring further.
Event Schema
Implementation
Best Practices
Overview
Consistent video event tracking across CTV platforms enables unified audience analytics and cross-platform cohort building. This guide covers the standard video event schema and implementation patterns for all supported platforms.Video Event Schema
Permutive uses a consistent video event schema across all CTV platforms:Core Events
| Event | Description | Trigger |
|---|---|---|
| Videoview | User initiated video playback | When video starts or user selects content |
| VideoEngagement | Internal SDK event — see callout below | Periodically during playback (on-device only) |
| VideoCompletion | User finished watching | When video ends or user exits |
| VideoAdView | Video ad started playing | When ad playback begins |
| VideoAdCompletion | Video ad finished | When ad playback ends |
| VideoAdClicked | User clicked video ad | When user interacts with ad |
Standard Video Properties
Use these properties consistently across events for unified analytics:Property Naming: The schema uses
snake_case property names, nested under a top-level video parent. Each platform’s SDK or addon handles input differently — see Property Mapping below for the per-platform details.| Property | Type | Description |
|---|---|---|
video.title | string | Video title |
video.genre | string[] | List of genres (e.g., [“Drama”, “Thriller”]) |
video.content_type | string[] | Content types (e.g., [“Series”, “Episode”]) |
video.age_rating | string | Age rating (e.g., “PG-13”, “TV-MA”) |
video.runtime | number | Runtime in seconds |
video.country | string | Origin country |
video.original_language | string | Original language code |
video.audio_language | string | Audio language being watched |
video.subtitles.enabled | boolean | Whether subtitles are enabled |
video.subtitles.language | string | Subtitle language |
video.season_number | number | Season number (for series) |
video.episode_number | number | Episode number (for series) |
video.consecutive_episodes | number | Consecutive episodes watched |
video.iab_categories | string[] | IAB content taxonomy categories |
Completion Event Properties
Additional properties onVideoCompletion (aggregated from VideoEngagement data accumulated during playback):
| Property | Type | Description |
|---|---|---|
aggregations.VideoEngagement.completion | number | Percentage watched (0.0 - 1.0) |
aggregations.VideoEngagement.engaged_time | number | Time spent watching in seconds |
Ad Event Properties
Properties for video ad events (VideoAdView, VideoAdCompletion, VideoAdClicked):
| Property | Type | Description |
|---|---|---|
ad.title | string | Ad title |
ad.duration | number | Ad duration in seconds |
ad.muted | boolean | Whether the ad was muted |
ad.campaign_id | string | Campaign identifier |
ad.creative_id | string | Creative identifier |
Ad Completion Event Properties
Additional properties onVideoAdCompletion:
| Property | Type | Description |
|---|---|---|
aggregations.VideoAdEngagement.completion | number | Percentage of ad watched (0.0 - 1.0) |
aggregations.VideoAdEngagement.engaged_time | number | Time spent watching the ad in seconds |
Platform Implementation
- Web CTV
- tvOS
- Android TV
- Roku
Platforms: Samsung Tizen, LG WebOS, HbbTVUse the CTV addon for automatic event tracking:Automatic Events:
Videoview- tracked when addon is initializedVideoCompletion- tracked when.stop()is called
Engagement Tracking
How Engagement Works
Engagement time measures how long a user actively watches content:- Start - When
play()is called, engagement timer starts - Pause - When
pause()is called, timer pauses - Resume - When
play()is called again, timer resumes - Complete - When
stop()is called, total engagement is recorded
Buffering Considerations
Buffering should pause engagement tracking:- Web CTV
- Native (iOS/Android)
Seeking/Scrubbing
When users seek to a new position:Ad Break Handling
Pre-roll Ads
Track ads before main content:Mid-roll Ads
Pause main content tracking during ads:Post-roll Ads
Track ads after main content completes:Best Practices
- Do
- Don't
- Set duration accurately - Provide video duration in milliseconds when initializing
- Sync with player state - Call
play()/pause()when the player state changes - Always call stop() - Ensure
stop()is called when video ends or user exits - Handle buffering - Pause tracking during buffering states
- Track all ad events - Capture ad views and completions for ad analytics
- Use consistent properties - Follow the standard schema across all platforms
- Include video metadata - Richer metadata enables more precise cohort building
- Generate unique view IDs - Create new view IDs per content session (Roku)
Cross-Platform Consistency
For unified analytics across platforms, ensure:Event Name Consistency
| Platform | Videoview Event | Completion Event | Ad View Event |
|---|---|---|---|
| Web CTV | Videoview (auto via addon) | VideoCompletion (auto via addon) | VideoAdView (manual via .track()) |
| tvOS | Videoview (auto via MediaTracker) | VideoCompletion (auto via MediaTracker) | VideoAdView (manual via track()) |
| Android TV | Videoview (auto via MediaTracker) | VideoCompletion (auto via MediaTracker) | VideoAdView (auto via VideoAdTracker) |
| Roku | Videoview (manual) | VideoCompletion (manual) | VideoAdView (manual) |
Property Mapping
Each platform exposes a different publisher input shape, but all platforms ultimately produce events that conform to the schema above (properties nested under a top-levelvideo parent).
| Platform | Publisher input shape | Mapping behavior |
|---|---|---|
| Web CTV (addon) | Flat videoProperties: { title, content_type, ... } in snake_case | Addon auto-nests publisher properties under video |
| Android SDK | Typed MediaTracker.VideoProperties(title, contentType, ...) in camelCase | SDK maps camelCase → snake_case and nests under video |
| iOS SDK | Untyped EventProperties dictionary; publisher must hand-build ["video": ["title": ..., "content_type": ...]] | No auto-mapping or auto-nesting — publishers write snake_case directly under a video key |
| Roku | Manually constructed event dict with properties: { video: { ... } } | None — publisher controls the full event shape |
Work with Technical Services to configure a unified event schema across all platforms.
Troubleshooting
Engagement time is incorrect
Engagement time is incorrect
Problem: Engaged time doesn’t match expected values.Solutions:
- Ensure
play()is called only when video is actually playing - Call
pause()during buffering and paused states - Verify
stop()is called when video ends - Check that ad breaks pause the main content tracker
Completion percentage is 0 or missing
Completion percentage is 0 or missing
Problem: VideoCompletion event shows 0% or null completion.Solutions:
- Provide duration when creating the tracker/addon
- Ensure duration is in the correct unit (milliseconds for most SDKs)
- Wait for video metadata to load before creating tracker
Video events not appearing in dashboard
Video events not appearing in dashboard
Problem: Events don’t show in Permutive analytics.Solutions:
- Verify SDK is initialized with correct credentials
- Check browser/device console for errors
- Ensure events match your workspace schema
- Wait 5-10 minutes for events to process
Ad events not linked to video sessions
Ad events not linked to video sessions
Problem: Ad events aren’t associated with video content.Solutions:
- Track ads using the same addon/tracker instance
- On Roku, use the same
view_idfor content and ads - Ensure ad events fire during the video session
Related Documentation
CTV Overview
Platform selection guide
Web CTV
Tizen, WebOS, HbbTV integration
tvOS
tvOS integration
Android TV
Android TV integration
Roku
Roku integration
iOS Video Tracking
Detailed iOS MediaTracker docs