> For the complete documentation index, see [llms.txt](https://help.citrusad.com/retail-media-interface/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.citrusad.com/retail-media-interface/integration/data-api/api-overview/ad-interaction-events-reporting/integrate-banner-x-video-interaction-reporting.md).

# Integrate Banner X Video Interaction Reporting

## 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](#alternative--fire-beacons-from-player-callbacks) 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**](https://developers.citrusad.com/integration/docs/ad-interaction-events-technical-reference).

## Prerequisites

| Prerequisite                                                                                                               | Why it matters                                                                                                                     |
| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Read the [Technical Reference](https://developers.citrusad.com/integration/docs/ad-interaction-events-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}
  &timestamp=[TIMESTAMP]
```

* `adId` — read `citrusAdId` from the served banner.
* `videoId` — read `idValue` from 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's `idValue`, so the funnel ties back to the video that was actually watched.
* `sessionId` — inject your `sessionId` (or `customerId` / `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

Add 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`).

```xml
<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&timestamp=[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&timestamp=[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&timestamp=[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&timestamp=[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&timestamp=[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&timestamp=[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&timestamp=[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&timestamp=[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&timestamp=[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&timestamp=[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&timestamp=[TIMESTAMP]]]></Tracking>
    </TrackingEvents>
  </Linear>
</Creative>
```

{% hint style="info" %}
**Multiple videos in one ad** Repeat the `<TrackingEvents>` block for every `<Creative>`, each using its own `UniversalAdId` `idValue` as `videoId`. **Never share one** `idValue` **across creatives — that's what lets Epsilon attribute the funnel to the specific video served.**
{% endhint %}

## 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

```javascript
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

```http
GET https://integration.retailer.citrusad.com/v1/events/ad/interaction
  ?adId=banner_vid001
  &timestamp=2026-05-20T10:20:00Z
  &sessionId=sess_001
  &interactionType=videoPlay
  &videoId=00000000-0000-0000-0000-000000000003
```

### Quartile milestone

Same shape for `videoFirstQuartile` / `videoMidpoint` / `videoThirdQuartile` / `videoComplete`.

```http
GET https://integration.retailer.citrusad.com/v1/events/ad/interaction
  ?adId=banner_vid001
  &timestamp=2026-05-20T10:20:15Z
  &sessionId=sess_001
  &interactionType=videoFirstQuartile
  &videoId=00000000-0000-0000-0000-000000000003
```

### Control event

Same shape for `videoSkip` / `videoPause` / `videoResume` / `videoMute` / `videoUnmute`.

```http
GET https://integration.retailer.citrusad.com/v1/events/ad/interaction
  ?adId=banner_vid001
  &timestamp=2026-05-20T10:20:40Z
  &sessionId=sess_001
  &interactionType=videoSkip
  &videoId=00000000-0000-0000-0000-000000000003
```

## What 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](https://developers.citrusad.com/integration/docs/ad-interaction-events-technical-reference) troubleshooting section.

<br>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://help.citrusad.com/retail-media-interface/integration/data-api/api-overview/ad-interaction-events-reporting/integrate-banner-x-video-interaction-reporting.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
