Skip to main content

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

EventDescriptionTrigger
VideoviewUser initiated video playbackWhen video starts or user selects content
VideoEngagementInternal SDK event — see callout belowPeriodically during playback (on-device only)
VideoCompletionUser finished watchingWhen video ends or user exits
VideoAdViewVideo ad started playingWhen ad playback begins
VideoAdCompletionVideo ad finishedWhen ad playback ends
VideoAdClickedUser clicked video adWhen 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.
PropertyTypeDescription
video.titlestringVideo title
video.genrestring[]List of genres (e.g., [“Drama”, “Thriller”])
video.content_typestring[]Content types (e.g., [“Series”, “Episode”])
video.age_ratingstringAge rating (e.g., “PG-13”, “TV-MA”)
video.runtimenumberRuntime in seconds
video.countrystringOrigin country
video.original_languagestringOriginal language code
video.audio_languagestringAudio language being watched
video.subtitles.enabledbooleanWhether subtitles are enabled
video.subtitles.languagestringSubtitle language
video.season_numbernumberSeason number (for series)
video.episode_numbernumberEpisode number (for series)
video.consecutive_episodesnumberConsecutive episodes watched
video.iab_categoriesstring[]IAB content taxonomy categories

Completion Event Properties

Additional properties on VideoCompletion (aggregated from VideoEngagement data accumulated during playback):
PropertyTypeDescription
aggregations.VideoEngagement.completionnumberPercentage watched (0.0 - 1.0)
aggregations.VideoEngagement.engaged_timenumberTime spent watching in seconds

Ad Event Properties

Properties for video ad events (VideoAdView, VideoAdCompletion, VideoAdClicked):
PropertyTypeDescription
ad.titlestringAd title
ad.durationnumberAd duration in seconds
ad.mutedbooleanWhether the ad was muted
ad.campaign_idstringCampaign identifier
ad.creative_idstringCreative identifier

Ad Completion Event Properties

Additional properties on VideoAdCompletion:
PropertyTypeDescription
aggregations.VideoAdEngagement.completionnumberPercentage of ad watched (0.0 - 1.0)
aggregations.VideoAdEngagement.engaged_timenumberTime spent watching the ad in seconds

Platform Implementation

Platforms: Samsung Tizen, LG WebOS, HbbTVUse the CTV addon for automatic event tracking:
// Initialize with video properties
permutive.addon("ctv", {
  duration: 3600000, // milliseconds
  videoProperties: {
    title: "Show Title - S1E1",
    genre: ["Drama", "Thriller"],
    season_number: 1,
    episode_number: 1,
    runtime: 3600
  }
})

// Sync with player events
videoElement.addEventListener("play", () => {
  permutive.addons.ctv.play(videoElement.currentTime * 1000)
})

videoElement.addEventListener("pause", () => {
  permutive.addons.ctv.pause(videoElement.currentTime * 1000)
})

// Track completion
const onVideoEnd = () => {
  permutive.addons.ctv.stop(videoElement.currentTime * 1000)
}

// Track ads manually
const onAdStart = (ad) => {
  permutive.addons.ctv.track("VideoAdView", {
    ad: {
      title: ad.title,
      duration: ad.duration,
      muted: ad.muted,
      campaign_id: ad.campaignId,
      creative_id: ad.creativeId
    }
  })
}
Automatic Events:
  • Videoview - tracked when addon is initialized
  • VideoCompletion - tracked when .stop() is called
See Web CTV documentation for full details.

Engagement Tracking

How Engagement Works

Engagement time measures how long a user actively watches content:
  1. Start - When play() is called, engagement timer starts
  2. Pause - When pause() is called, timer pauses
  3. Resume - When play() is called again, timer resumes
  4. Complete - When stop() is called, total engagement is recorded

Buffering Considerations

Buffering should pause engagement tracking:
videoElement.addEventListener("waiting", () => {
  permutive.addons.ctv.pause()
})

videoElement.addEventListener("playing", () => {
  permutive.addons.ctv.play()
})

Seeking/Scrubbing

When users seek to a new position:
// Web CTV
videoElement.addEventListener("seeked", () => {
  permutive.addons.ctv.play(videoElement.currentTime * 1000)
})
// Android
exoPlayer.addAnalyticsListener(object : AnalyticsListener {
    override fun onPositionDiscontinuity(
        eventTime: AnalyticsListener.EventTime,
        reason: Int
    ) {
        if (reason == Player.DISCONTINUITY_REASON_SEEK) {
            videoTracker.play(exoPlayer.currentPosition)
        }
    }
})

Ad Break Handling

Pre-roll Ads

Track ads before main content:
// Web CTV example
const onPrerollStart = (ad) => {
  permutive.addons.ctv.track("VideoAdView", {
    ad: {
      title: ad.title,
      duration: ad.duration,
      muted: ad.muted,
      campaign_id: ad.campaignId,
      creative_id: ad.creativeId
    }
  })
}

const onPrerollEnd = (ad) => {
  permutive.addons.ctv.track("VideoAdCompletion", {
    ad: {
      title: ad.title,
      campaign_id: ad.campaignId,
      creative_id: ad.creativeId
    }
  })

  // Now start main content
  permutive.addon("ctv", {
    duration: videoDuration,
    videoProperties: { title: "Show Title" }
  })
  permutive.addons.ctv.play()
}

Mid-roll Ads

Pause main content tracking during ads:
const onMidrollStart = (ad) => {
  // Pause main content tracking
  permutive.addons.ctv.pause()

  // Track ad
  permutive.addons.ctv.track("VideoAdView", {
    ad: {
      title: ad.title,
      duration: ad.duration,
      muted: ad.muted,
      campaign_id: ad.campaignId,
      creative_id: ad.creativeId
    }
  })
}

const onMidrollEnd = (ad) => {
  permutive.addons.ctv.track("VideoAdCompletion", {
    ad: {
      title: ad.title,
      campaign_id: ad.campaignId,
      creative_id: ad.creativeId
    }
  })

  // Resume main content tracking
  permutive.addons.ctv.play()
}

Post-roll Ads

Track ads after main content completes:
const onContentEnd = () => {
  // Complete main content tracking first
  permutive.addons.ctv.stop()
}

const onPostrollStart = (ad) => {
  permutive.addons.ctv.track("VideoAdView", {
    ad: {
      title: ad.title,
      duration: ad.duration,
      muted: ad.muted,
      campaign_id: ad.campaignId,
      creative_id: ad.creativeId
    }
  })
}

Best Practices

  • 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

PlatformVideoview EventCompletion EventAd View Event
Web CTVVideoview (auto via addon)VideoCompletion (auto via addon)VideoAdView (manual via .track())
tvOSVideoview (auto via MediaTracker)VideoCompletion (auto via MediaTracker)VideoAdView (manual via track())
Android TVVideoview (auto via MediaTracker)VideoCompletion (auto via MediaTracker)VideoAdView (auto via VideoAdTracker)
RokuVideoview (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-level video parent).
PlatformPublisher input shapeMapping behavior
Web CTV (addon)Flat videoProperties: { title, content_type, ... } in snake_caseAddon auto-nests publisher properties under video
Android SDKTyped MediaTracker.VideoProperties(title, contentType, ...) in camelCaseSDK maps camelCasesnake_case and nests under video
iOS SDKUntyped 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
RokuManually 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

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
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
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
Problem: Ad events aren’t associated with video content.Solutions:
  • Track ads using the same addon/tracker instance
  • On Roku, use the same view_id for content and ads
  • Ensure ad events fire during the video session

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