Learning Track

VAST & Video

Read, validate, and debug VAST 4.1 tags — tracking events, MediaFile requirements, wrapper chains, and CTV delivery failures.

Intermediate track

What to look for before you start debugging tags and manifests

This page starts at an intermediate level. You already know that VAST serves video ads. The goal here is to help you read VAST tags line by line, validate event coverage, understand wrapper chains, and connect CTV failures back to specific XML problems.

What this page covers

  • VAST 4.1 InLine structure with every required and key optional element.
  • Tracking event reference — which events must fire and when.
  • Wrapper chains: how they work, how many hops are too many, and how they fail.

What is inside

  • Fully annotated VAST 4.1 XML example with MediaFile, tracking, clicks.
  • Tracking event and error code reference tables.
  • Common failure patterns with diagnostic steps for each.

Best way to use it

  • Parse a live VAST tag line by line before checking playback or event firing.
  • Use the IAB VAST Inspector to validate any tag before trafficking.
  • Map every tracking event to the player action that should fire it.

Why VAST fluency matters

VAST (Video Ad Serving Template) is the XML standard that tells a video player how to play an ad: what video file to load, how long it runs, which URLs to ping at each quartile, and where to send the user when they click. If any element is wrong — a missing Impression URL, a tracking event with a malformed URL, a MediaFile with an unsupported codec for CTV — the ad either doesn’t play or plays without reporting delivery back to the DSP. TAMs diagnose these issues daily when advertisers report discrepancies between their ad server numbers and the publisher’s reporting.

VAST document structure — the key hierarchy

  • VAST → root element with version attribute. Can contain one or more Ad elements.
  • Ad → contains either InLine (the actual creative) or Wrapper (a redirect to another VAST).
  • InLine → Creatives → Creative → Linear → the main video creative definition. Contains Duration, MediaFiles, TrackingEvents, VideoClicks.
  • TrackingEvents → list of Tracking elements, each specifying an event attribute and a URL. The player fires each URL at the corresponding moment during playback.
  • MediaFiles → one or more MediaFile elements pointing to video files. The player chooses the best match for its bitrate/codec support.
  • VideoClicks → ClickThrough / ClickTracking → the landing page URL and optional click-tracking pixels.

Annotated VAST 4.1 InLine example

A complete, production-valid VAST 4.1 tag for a 30-second skippable linear video ad. Every element is explained inline.

XML VAST 4.1 — Complete InLine with tracking, MediaFiles, and VideoClicks
<?xml version="1.0" encoding="UTF-8"?>
<VAST version="4.1">

  <Ad id="ad-001" sequence="1">            <!-- sequence: pod position (1 = first ad) -->

    <InLine>
      <AdSystem version="4.1">My Ad Server</AdSystem>
      <AdTitle>Samsung TV - 30s Spot</AdTitle>

      <!-- Impression: fires when the player confirms it has loaded the VAST and is ready to play -->
      <Impression id="imp-001">
        <![CDATA[https://track.example.com/imp?aid=001&cb=__random__]]>
      </Impression>

      <!-- Second impression URL — both fire on the same event (common for dual-pixel attribution) -->
      <Impression id="imp-002">
        <![CDATA[https://dsp.example.com/imp?crid=55&type=vast]]>
      </Impression>

      <AdVerifications>             <!-- OMID viewability — required for IAS, DoubleVerify integration -->
        <Verification vendor="doubleverify.com-omid">
          <JavaScriptResource apiFramework="omid" browserOptional="true">
            <![CDATA[https://cdn.doubleverify.com/dvtp_src.js]]>
          </JavaScriptResource>
        </Verification>
      </AdVerifications>

      <Creatives>
        <Creative id="cr-001" sequence="1" adId="ad-001">
          <Linear skipoffset="00:00:05">     <!-- Skip button appears after 5 seconds -->

            <Duration>00:00:30</Duration>                <!-- HH:MM:SS — must match actual video duration -->

            <TrackingEvents>
              <!-- Required IAB events: muted, unmuted, pause, resume, rewind, skip, playerExpand, playerCollapse -->
              <!-- Quartile events below are the most tracked by DSPs -->
              <Tracking event="start"><![CDATA[https://track.example.com/start?cb=__r__]]></Tracking>
              <Tracking event="firstQuartile"><![CDATA[https://track.example.com/q1?cb=__r__]]></Tracking>
              <Tracking event="midpoint"><![CDATA[https://track.example.com/mid?cb=__r__]]></Tracking>
              <Tracking event="thirdQuartile"><![CDATA[https://track.example.com/q3?cb=__r__]]></Tracking>
              <Tracking event="complete"><![CDATA[https://track.example.com/complete?cb=__r__]]></Tracking>
              <Tracking event="mute"><![CDATA[https://track.example.com/mute?cb=__r__]]></Tracking>
              <Tracking event="unmute"><![CDATA[https://track.example.com/unmute?cb=__r__]]></Tracking>
              <Tracking event="pause"><![CDATA[https://track.example.com/pause?cb=__r__]]></Tracking>
              <Tracking event="resume"><![CDATA[https://track.example.com/resume?cb=__r__]]></Tracking>
              <Tracking event="skip"><![CDATA[https://track.example.com/skip?cb=__r__]]></Tracking>
              <Tracking event="progress" offset="00:00:10">    <!-- Custom progress at 10s -->
                <![CDATA[https://track.example.com/10s?cb=__r__]]>
              </Tracking>
            </TrackingEvents>

            <MediaFiles>
              <!-- Primary: HD MP4 for web and CTV -->
              <MediaFile
                delivery="progressive"         <!-- progressive = download; streaming = HLS/DASH -->
                type="video/mp4"
                width="1920" height="1080"
                bitrate="2500"                 <!-- Kbps — player picks closest match to connection speed -->
                codec="H.264">
                <![CDATA[https://cdn.example.com/ads/spot-1080p.mp4]]>
              </MediaFile>
              <!-- Fallback: lower bitrate for slower connections -->
              <MediaFile
                delivery="progressive"
                type="video/mp4"
                width="1280" height="720"
                bitrate="1200"
                codec="H.264">
                <![CDATA[https://cdn.example.com/ads/spot-720p.mp4]]>
              </MediaFile>
              <!-- VPAID/SIMID interactive: for desktop only — avoid on CTV -->
              <InteractiveCreativeFile
                type="text/javascript"
                apiFramework="simid"
                variableDuration="false">
                <![CDATA[https://cdn.example.com/ads/simid.js]]>
              </InteractiveCreativeFile>
            </MediaFiles>

            <VideoClicks>
              <ClickThrough id="ct-001">
                <![CDATA[https://advertiser.example.com/landing?src=ctv]]>
              </ClickThrough>
              <ClickTracking id="ctr-001">   <!-- Fires on click alongside ClickThrough -->
                <![CDATA[https://track.example.com/click?aid=001]]>
              </ClickTracking>
            </VideoClicks>

          </Linear>
        </Creative>
      </Creatives>

      <Extensions>
        <Extension type="iab-Count">
          <Count type="impression">1</Count>
        </Extension>
      </Extensions>

    </InLine>
  </Ad>
</VAST>
  • Duration Must exactly match the video file length in HH:MM:SS. A VAST that says 00:00:30 but the MP4 is 29.97s causes player inconsistency — some players cut playback early, others extend black frames.
  • skipoffset The second at which the skip button appears. IAB requires 5s minimum for skippable ads. Missing this attribute = ad is non-skippable. Incorrect value = compliance violation in some markets.
  • CDATA wrapping All URLs in tracking events must be wrapped in CDATA: <![CDATA[url]]>. Without CDATA, URL characters like &, <, > break XML parsing and cause a VAST 400 error. This is one of the most common VAST bugs.
  • MediaFile type Must be a valid MIME type: video/mp4, video/webm. Incorrect MIME type causes the player to reject the MediaFile even if the actual video would play fine.
  • MediaFile codec CTV devices (Roku, FireTV, Samsung Tizen) require H.264 (AVC). HEVC (H.265) is supported on some but not all. VP9 is not supported on most CTV platforms. Sending only VP9 MediaFiles to a CTV player = black screen.
  • Impression vs Tracking start Impression fires when VAST is loaded by the player (before playback). The start tracking event fires when the first frame plays. These are different events — a discrepancy between impression count and start count indicates ads loading but not playing.

VAST Wrapper chains

A Wrapper is a VAST response that redirects to another VAST URL instead of containing an InLine creative. Wrappers are used by ad networks to pass through demand to underlying DSPs. They add latency with each hop.

XML VAST Wrapper — redirects to another VAST URL
<?xml version="1.0" encoding="UTF-8"?>
<VAST version="4.1">
  <Ad id="wrapper-001">

    <Wrapper followAdditionalWrappers="true">
                  <!-- followAdditionalWrappers: true = keep following Wrapper chains; false = stop -->
      <AdSystem>SSP Reseller Layer</AdSystem>

      <VASTAdTagURI>
        <!-- URL the player/SSAI fetches next — this is the "next hop" in the chain -->
        <![CDATA[https://dsp.example.com/vast?bid=xyz&cb=__random__]]>
      </VASTAdTagURI>

      <!-- Impression from the wrapper layer — fires in addition to InLine impressions -->
      <Impression><![CDATA[https://ssp.example.com/imp?w=1]]></Impression>

      <Creatives>
        <Creative><Linear>
          <TrackingEvents>
            <!-- Wrapper-level tracking fires alongside InLine tracking -->
            <Tracking event="complete"><![CDATA[https://ssp.example.com/complete?w=1]]></Tracking>
          </TrackingEvents>
        </Linear></Creative>
      </Creatives>

    </Wrapper>
  </Ad>
</VAST>

<!-- ===== Chain resolves to InLine after 2 more Wrapper hops: =====
  Hop 1: Publisher ad server → SSP Wrapper (this file)
  Hop 2: SSP → DSP Wrapper
  Hop 3: DSP → InLine (final creative, media files, all tracking URLs)

  Impressions that fire:
  - publisher-ad-server impression
  - ssp.example.com/imp?w=1
  - dsp.example.com/imp (from hop 2 wrapper)
  - dsp.example.com/imp (from InLine)
  Total: 4 impression pixels for one ad play.
===== -->
Wrapper chain depth limit: most players and SSAI systems enforce a max of 3–5 wrapper hops. Each hop adds network latency — at 150 ms per hop, a 5-hop chain adds 750 ms before the creative starts loading. CTV devices with slower CPUs time out at 3 hops routinely. A VAST 303 error (“no VAST response after timeout”) is almost always a wrapper chain depth or latency issue.

Tracking event reference

VAST 4.1 defines these player-fired events. All URLs must be inside CDATA. Quartile events are the most commonly audited — missing any of them causes DSP reporting discrepancies.

Event nameWhen it firesRequired?Common issue
impressionVAST loaded by player (before playback)YesFires even if ad never plays — not a playback confirmation
startFirst frame of video rendersYesGap between impression and start count = ads loaded but not playing
firstQuartile25% of duration playedYesMissing = DSP cannot calculate view-through attribution at 25%
midpoint50% of duration playedYesMost DSPs bill on midpoint — missing means no billing signal
thirdQuartile75% of duration playedYesUsed for viewability measurement
complete100% of ad playedYesBrands report completion rate — missing breaks campaign reporting
mute / unmuteUser clicks mute/unmute buttonRecommendedMissing = brand engagement data incomplete
pause / resumeUser pauses/resumes playbackRecommendedPause during ad does not reset quartile tracking — player must resume from where it paused
skipUser clicks skip buttonIf skipoffset setMust be present if ad is skippable. Missing = no skip reporting to DSP
progressCustom offset (e.g. 10s mark)OptionalUse for custom measurement points (e.g. “watched 10s of a 30s ad”)
clickThroughUser clicks the ad creativeOptional (but tracked)CTV remote clicks do not trigger clickThrough — CTV campaigns often show 0 clicks

VAST error codes

The <Error> element in a VAST tag defines a URL that fires when the player encounters an error. The [ERRORCODE] macro is replaced by the player with the numeric error code.

XML VAST Error element in InLine
<!-- Add inside <InLine> or <Wrapper> to receive error notifications -->
<Error>
  <![CDATA[https://track.example.com/vast-error?code=[ERRORCODE]&aid=001]]>
</Error>
<!-- The player replaces [ERRORCODE] with the numeric error at runtime -->
<!-- Example fired URL: https://track.example.com/vast-error?code=403&aid=001 -->
CodeMeaningCommon causeFix
100XML parse errorMalformed XML, unescaped & in URLValidate with IAB VAST Inspector. Ensure all URLs are CDATA-wrapped.
101VAST schema validation errorRequired element missing (e.g. <Duration>)Run schema validation. Confirm InLine has AdSystem, Impression, Creatives, Duration, MediaFile.
102VAST version not supportedPlayer supports VAST 2.0 only; tag uses VAST 4.1 elementsProvide VAST 2.0 fallback for older players. CTV players often need 4.0+ for proper tracking.
200Trafficking errorWrong ad unit trafficked to wrong playerVerify the ad unit type matches player type (linear video for in-stream player).
300Wrapper timeoutWrapper URL did not respond in timeReduce wrapper chain depth. Increase player VAST timeout if configurable. Check DSP latency.
301Wrapper no adsWrapped VAST returned empty responseCheck if the DSP has fill for the request parameters. Add a fallback VAST URL on the SSP side.
303No VAST after max wrappersChain exceeded player’s max hop limit (typically 5)Flatten the chain. Ask the DSP to return InLine directly or limit to 2 wrapper hops.
400General MediaFile errorFile 404, wrong MIME type, or unsupported codecConfirm the MediaFile URL resolves. Verify MIME type is video/mp4. For CTV: confirm H.264.
401MediaFile timeoutVideo file loaded too slowly for the player bufferMove creative to a CDN closer to viewer geography. Check bitrate — 2500 Kbps may be too high for some connections.
403MediaFile unsupportedPlayer cannot play any of the provided MediaFilesAdd multiple MediaFile entries covering different codecs and bitrates. CTV always needs H.264 MP4.
900Undefined errorPlayer-specific issue not covered by IAB codesCheck player SDK release notes. Capture network HAR and player console log for the session.

Common VAST failure patterns

Impression fires but start never fires

VAST was loaded by the player (impression pixel hit) but playback never started. On CTV: the MediaFile codec or format was unsupported. Error code 403 in the error pixel. On web: VPAID/SIMID JS failed to load.

▸ Check MediaFile elements for H.264 MP4. Confirm delivery=“progressive” for CTV (not streaming). Validate that all MediaFile URLs are accessible from the player’s network — geo blocks or signed URL expiry are common culprits.

Complete never fires despite full view

DSP reports completion rate is 0% but the ad is playing fully. The complete tracking URL is malformed (unescaped characters) or the tracking domain is blocked by the CTV device’s network.

▸ Validate the complete tracking URL by pasting it directly into a browser. Check for & characters that should be &amp; in XML. Test from a CTV device’s network IP — some enterprise networks block third-party tracking domains.

VAST 303 — no InLine after wrappers

The player followed the wrapper chain but hit the hop limit (typically 5) before reaching an InLine. The wrapper chain is too deep — common when multiple layers of resellers are in the supply path.

▸ Use a VAST inspector tool to resolve the full wrapper chain manually. Count the hops. Ask the DSP to reduce to 2 hops maximum. If SSAI, confirm it is configured to follow at least 5 redirects.

Missing tracking events in QA

When validating a VAST tag, some quartile events are missing from the XML entirely. The DSP will show 0% completion rate or 0% quartile measurement even if the ad plays correctly.

▸ Run the tag through the IAB VAST Inspector — it reports missing required events. For each missing event, add a Tracking element with the correct event attribute and a valid CDATA-wrapped URL. All 4 quartile events + complete are minimum required.

Duration mismatch

VAST Duration says 00:00:30 but the video file is 29.97s or 31.5s. Some players cut the ad short at the declared duration; others wait for the full file. Creates inconsistent reporting and viewing experience.

▸ Inspect the actual MediaFile duration with ffprobe or a media info tool. Update VAST Duration to match. Always encode ads at exact target durations (30.000s, not 29.97s) before trafficking.

ClickThrough shows 0 clicks on CTV

CTV remote controls register clicks differently than mouse clicks. The VideoClicks/ClickThrough event may never fire, or fires but is not tracked. Many CTV platforms handle click-to-website differently (QR code, second screen).

▸ This is expected behavior on most CTV devices — do not report it as a bug. Instead, confirm whether the CTV device supports “click-to-QR” or “send to phone” features, and set up tracking for those interactions separately in the creative companion.

Practice drills and outputs

Drill 1 — Author a valid VAST 4.1 tag

  • Write a minimal valid VAST 4.1 InLine tag from scratch using the example above as a template.
  • Include two MediaFiles (1080p and 720p), all 4 quartile tracking events, impression, complete, skip.
  • Validate using the IAB VAST Inspector (available at iab.com). Fix all reported errors.
  • Output: vast-template.xml — reusable template for future QA.

Drill 2 — Parse 10 live VAST tags

  • Capture VAST tags from 10 different campaigns (from your ad server or a public VAST test URL library).
  • For each: list missing events, count wrapper hops, note MediaFile codecs and bitrates.
  • Flag any that would fail on CTV (wrong codec, no H.264, more than 3 wrapper hops).
  • Output: vast-audit.csv — tag, events present, hops, CTV compatible, issues.

Drill 3 — Error code simulation

  • Using a local video player (JW Player, Video.js) or a VAST testing tool, trigger each of the major VAST error codes deliberately.
  • For error 403: remove all MediaFile elements. For error 303: create a 6-hop wrapper chain.
  • Confirm the error pixel fires with the correct [ERRORCODE] value.
  • Output: vast-error-cheat-sheet.md — code, cause, fix, test method.

References

  • IAB VAST 4.1 specification — iab.com/guidelines
  • IAB VAST Inspector (online validator) — iabtech.com/vast-inspector
  • IAB VMAP 1.0 — for break scheduling in long-form video
  • Google DAI VAST tag guide — CTV-specific VAST requirements
  • ffprobe (FFmpeg) — inspect video file codec, duration, bitrate

AdTech Toolkit

Enter any two values
to calculate the third

More tools coming soon