What this turns on
Video reporting shows how far shoppers watch — creative view, play, quartile progress, and completion — plus control actions (skip, pause, mute). It works for Banner X video creative.
Banner X video is typically rendered through a VAST 4.0 player that you also have integrated, so the fastest way to enable this reporting is to let the player fire the events for you: Epsilon returns a VAST tag in the adm field of the ad response, and you inject a <TrackingEvents> block that points each playback milestone at the interaction endpoint. This guide shows how to build that block. If your player can't emit VAST tracking (or the video isn't served through VAST), use the manual beacon fallback instead.
This guide covers only the video interaction types and how to wire them through VAST. For the endpoint, authentication, core fields, beacon mechanics, dedup, and testing, see the Technical Reference.
Prerequisites
| Prerequisite | Why it matters |
|---|---|
| Read the Technical Reference | Covers the endpoint, authentication, core fields, and deduplication rules used by all video events. |
Served ads return a realised adId (citrusAdId) | Ensures every video interaction can be attributed back to the delivered ad. |
A VAST 4.0 capable player that renders the adm tag and fires <TrackingEvents> | The player fires the injected beacons on real playback progress. |
The player expands VAST macros (e.g. [TIMESTAMP], [CACHEBUSTING]) | Lets the player stamp each beacon at fire time instead of you inferring it. |
You read UniversalAdId idValue from each <Creative> | This is the stable, per-video id Epsilon uses as videoId to tie the funnel together (a single ad can carry more than one video). |
| Consistent tracking id per ad session | Use sessionId, customerId, or dtmToken consistently so reporting can join events across the session. |
What Epsilon serves today (and what you add)
The adm object in the Banner X response is a VAST 4.0 tag. Epsilon already wires impression and click tracking into it (<Impression> and <VideoClicks><ClickTracking>). It does not serve the progress and interaction tracking — that is what you inject.
The interaction endpoint already accepts every video type below. You bridge the two by adding a <TrackingEvents> block whose URLs are GET /v1/events/ad/interaction beacons. When the player crosses each milestone, it fires the matching URL. Leave the Epsilon-served <Impression> and <ClickTracking> nodes exactly as they are — you are only adding <TrackingEvents>.
Interaction Types for Video Reporting
Send the core fields plus videoId on every video event. iabConsentString is the optional field on all types (URL-encoded). Fire funnel events in order as the player reaches each milestone, reusing the same adId, videoId, and tracking id for the whole view session.
Funnel Events
interactionType | When to fire | Reporting purpose |
|---|---|---|
videoCreativeView | First video frame rendered | Video impression baseline |
videoPlay | Playback starts (user-initiated or autoplay per policy) | Video start rate |
videoFirstQuartile | 25% of duration watched | Video funnel Q1 |
videoMidpoint | 50% watched | Video funnel Q2 |
videoThirdQuartile | 75% watched | Video funnel Q3 |
videoComplete | 100% watched | Completion rate — creative effectiveness and video spend justification |
Control Events
interactionType | When to fire | Reporting purpose |
|---|---|---|
videoSkip | User skips before complete | Skip rate — drop-off / creative issues |
videoPause | User pauses | Engagement depth / distraction |
videoResume | User resumes after pause | Re-engagement after pause |
videoMute | User mutes audio | Audio engagement preference |
videoUnmute | User unmutes | Active audio interest |
Map VAST events to Epsilon interaction types
VAST players fire standard <Tracking event="…"> callbacks. Inject one <Tracking> node per row below, pointing at the interaction endpoint with the mapped interactionType.
VAST <Tracking event> | Epsilon interactionType |
|---|---|
creativeView | videoCreativeView |
start | videoPlay |
firstQuartile | videoFirstQuartile |
midpoint | videoMidpoint |
thirdQuartile | videoThirdQuartile |
complete | videoComplete |
skip | videoSkip |
pause | videoPause |
resume | videoResume |
mute | videoMute |
unmute | videoUnmute |
Other VAST events with no mention above (e.g. progress, fullscreen, exitFullscreen, rewind, close) are not part of Epsilon video reporting — don't inject beacons for them.
Construct the tracking URL
Each injected <Tracking> URL is a single, URL-encoded GET beacon. Populate it at build time with values from the ad response, and use a VAST macro for the timestamp so the player stamps it at fire time.
https://integration.{url}.citrusad.com/v1/events/ad/interaction
?adId={citrusAdId}
&interactionType={mapped type, e.g. videoFirstQuartile}
&videoId={UniversalAdId idValue for this creative}
&sessionId={your session tracking id}
×tamp=[TIMESTAMP]
adId— readcitrusAdIdfrom the served banner.videoId— readidValuefrom the<UniversalAdId>of the creative you're wiring. This is per-creative: if an ad carries multiple videos, each<Creative>gets its own<TrackingEvents>block using that creative'sidValue, so the funnel ties back to the video that was actually watched.sessionId— inject yoursessionId(orcustomerId/dtmToken) at build time; the endpoint rejects events with no tracking id.timestamp— use the[TIMESTAMP]VAST macro so the player substitutes the real ISO 8601 fire time. If your player doesn't support it, stamp the beacon another way, but don't hard-code a single time for all events.- Add
[CACHEBUSTING]as a throwaway parameter if your player caches identical URLs.
Inject <TrackingEvents> into the served VAST tag
<TrackingEvents> into the served VAST tagAdd a <TrackingEvents> block inside each <Creative>'s <Linear> element (after <VideoClicks>, matching the VAST 4.0 sample). Below, the <Impression> and <ClickTracking> are Epsilon-served and left untouched; the highlighted <TrackingEvents> block is what you add. Note videoId reuses this creative's idValue (…000003).
<Creative>
<UniversalAdId idRegistry="citrusad.com" idValue="00000000-0000-0000-0000-000000000003">
00000000-0000-0000-0000-000000000003
</UniversalAdId>
<Linear>
<Duration>00:00:15</Duration>
<MediaFiles>
<MediaFile delivery="progressive" type="video/mp4" width="1920" height="1080">
<![CDATA[https://example.com/media/example-video-2.mp4]]>
</MediaFile>
</MediaFiles>
<VideoClicks>
<ClickTracking>
<![CDATA[https://integration.{retailer}.citrusad.com/v1/resource/second-c/example_ad_id]]>
</ClickTracking>
<ClickThrough></ClickThrough>
</VideoClicks>
<!-- Injected by the retailer: maps VAST playback events to the Epsilon interaction endpoint -->
<TrackingEvents>
<Tracking event="creativeView"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoCreativeView&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="start"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoPlay&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="firstQuartile"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoFirstQuartile&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="midpoint"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoMidpoint&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="thirdQuartile"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoThirdQuartile&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="complete"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoComplete&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="skip"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoSkip&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="pause"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoPause&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="resume"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoResume&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="mute"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoMute&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
<Tracking event="unmute"><![CDATA[https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?adId=example_ad_id&interactionType=videoUnmute&videoId=00000000-0000-0000-0000-000000000003&sessionId=sess_001×tamp=[TIMESTAMP]]]></Tracking>
</TrackingEvents>
</Linear>
</Creative>
Multiple videos in one adRepeat the
<TrackingEvents>block for every<Creative>, each using its ownUniversalAdIdidValueasvideoId. Never share oneidValueacross creatives — that's what lets Epsilon attribute the funnel to the specific video served.
Playback Sequence
Funnel events follow this order over a full view:
videoCreativeView → videoPlay → videoFirstQuartile → videoMidpoint → videoThirdQuartile → videoComplete
| Rule | Guidance |
|---|---|
| Funnel milestones | Must be tied to real playback progress. VAST players fire quartiles on genuine progress; don't synthesise them on seek. |
| Control events | videoSkip, videoPause, videoResume, videoMute, and videoUnmute may fire at any point during playback. |
| Session attribution | Reuse the same adId, videoId (idValue), and tracking id across the session so the funnel ties together. |
Alternative — fire beacons from player callbacks
If your player can't emit VAST <TrackingEvents>, or the video isn't served through VAST, fire the same beacons directly from player callbacks. The endpoint and fields are identical — you're just constructing the URL in code instead of in the VAST tag.
Step 1 — Capture context
Read adId (citrusAdId) and the creative's UniversalAdId idValue to use as videoId. Reuse the session tracking id.
Step 2 — Fire funnel milestones from player callbacks
function fireVideo(interactionType) {
navigator.sendBeacon(
"https://integration.{retailer}.citrusad.com/v1/events/ad/interaction?" +
new URLSearchParams({
adId, interactionType, videoId, sessionId,
timestamp: new Date().toISOString()
})
);
}
// e.g. player.on("firstquartile", () => fireVideo("videoFirstQuartile"));Step 3 — Fire control events as they happen
Hook skip/pause/resume/mute/unmute and fire the matching type. Debounce rapid toggles.
Sample Requests
The injected VAST URLs and the manual beacons resolve to the same GET request. Full URLs below are line-wrapped for readability — send as a single encoded query string.
Video play
GET https://integration.retailer.citrusad.com/v1/events/ad/interaction
?adId=banner_vid001
×tamp=2026-05-20T10:20:00Z
&sessionId=sess_001
&interactionType=videoPlay
&videoId=00000000-0000-0000-0000-000000000003Quartile milestone
Same shape for videoFirstQuartile / videoMidpoint / videoThirdQuartile / videoComplete.
GET https://integration.retailer.citrusad.com/v1/events/ad/interaction
?adId=banner_vid001
×tamp=2026-05-20T10:20:15Z
&sessionId=sess_001
&interactionType=videoFirstQuartile
&videoId=00000000-0000-0000-0000-000000000003Control event
Same shape for videoSkip / videoPause / videoResume / videoMute / videoUnmute.
GET https://integration.retailer.citrusad.com/v1/events/ad/interaction
?adId=banner_vid001
×tamp=2026-05-20T10:20:40Z
&sessionId=sess_001
&interactionType=videoSkip
&videoId=00000000-0000-0000-0000-000000000003What Good Looks Like
| Area | Expected outcome |
|---|---|
| Injected tracking | Each <Creative> carries one <TrackingEvents> block; every <Tracking> URL uses that creative's idValue as videoId. |
| Quartile events | Quartiles arrive in order, with exactly one videoComplete for a full watch. |
| Control events | Events don't duplicate beyond the deduplication rules. |
| Re-watches | Re-watches in the same session reuse videoId and remain attributable. |
Troubleshooting (Video specifics)
| Issue | Likely cause | Action |
|---|---|---|
| No progress events arrive, only impression/click | <TrackingEvents> not injected, or added outside <Linear>. | Add the block inside each <Creative>'s <Linear> and confirm the player parses it. |
| All events share one timestamp | [TIMESTAMP] macro not expanded by the player. | Confirm macro support, or stamp per fire; don't hard-code one time. |
| Events land but can't be attributed to a video | videoId missing or reused across creatives. | Set videoId to each creative's own UniversalAdId idValue. |
| Events return HTTP 400 | Unencoded URL, missing tracking id, or unknown interactionType. | URL-encode the whole <Tracking> URL; include sessionId/customerId/dtmToken; use the exact mapped type. |
| Events appear at fixed intervals rather than matching playback | Beacons fired on a timer instead of real progress. | Bind each event to the player's actual progress callbacks (VAST does this for you). |
videoComplete fires more than once | Completion handler also fires on loop or replay. | Debounce and gate to a single completion per playback. |
videoComplete fires on load | The completion event is tied to load rather than 100% playback. | Tie videoComplete to genuine end-of-playback. |
For general endpoint troubleshooting, see the Technical Reference troubleshooting section.

