What this page covers
- How SSAI works vs CSAI — server-side vs client-side ad insertion.
- HLS manifest structure with SCTE-35 and EXT-X-DATERANGE ad markers.
- VMAP podding rules, break types, and ad sequencing.
Learning Track
Understand server-side ad stitching, HLS/DASH manifest structure, VMAP podding, and how to debug SSAI delivery failures.
Intermediate track
This page is written for an intermediate practitioner. It assumes you know the basic CTV and VAST vocabulary and want a more operational view: how SSAI stitches HLS/DASH, how podding rules work, and how to systematically debug manifest and beacon failures.
Client-Side Ad Insertion (CSAI) works on web browsers: the player calls an ad server, fetches a VAST tag, downloads the ad creative, and plays it. This works because desktop browsers handle VAST and can accept third-party JS. CTV devices — Smart TVs, Roku, FireTV, Apple TV — do not support third-party JavaScript or VAST redirects reliably. SSAI solves this by stitching ad segments directly into the content stream on the server, delivering a single seamless HLS or DASH feed that alternates between content and ad segments.
A stitched HLS manifest shows the transition from content to ad segments. The key markers that signal an ad break are EXT-X-DISCONTINUITY and EXT-X-DATERANGE.
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6 # Max segment duration — all segments must be ≤ 6s
#EXT-X-MEDIA-SEQUENCE:0
# === Content segments (pre-break) ===
#EXTINF:6.000,
https://cdn.example.com/content/seg-000.ts
#EXTINF:6.000,
https://cdn.example.com/content/seg-001.ts
#EXTINF:6.000,
https://cdn.example.com/content/seg-002.ts
# === Ad break starts ===
#EXT-X-DISCONTINUITY # Signals codec/timeline break — player resets sync
#EXT-X-DATERANGE:\
ID="ad-break-1",\ # Unique break identifier
CLASS="com.apple.hls.interstitial",\
START-DATE="2024-04-18T12:08:00Z",\ # Absolute wall-clock time of break start
PLANNED-DURATION=30.0,\ # Expected ad pod duration in seconds
SCTE35-OUT=0xFC301100000000FF00FFF00505000000007FEFFE # SCTE-35 splice insert signal
# Ad 1 — 15-second spot (3 segments × 5s each)
#EXTINF:5.000,
https://ssai.example.com/ad/creative-55/seg-000.ts
#EXTINF:5.000,
https://ssai.example.com/ad/creative-55/seg-001.ts
#EXTINF:5.000,
https://ssai.example.com/ad/creative-55/seg-002.ts
# Ad 2 — 15-second spot (3 segments × 5s each)
#EXTINF:5.000,
https://ssai.example.com/ad/creative-88/seg-000.ts
#EXTINF:5.000,
https://ssai.example.com/ad/creative-88/seg-001.ts
#EXTINF:5.000,
https://ssai.example.com/ad/creative-88/seg-002.ts
# === Content resumes ===
#EXT-X-DISCONTINUITY # Second discontinuity marks return to content
#EXT-X-DATERANGE:\
ID="ad-break-1",\
END-DATE="2024-04-18T12:08:30Z" # Closes the break — duration confirms 30s delivered
#EXTINF:6.000,
https://cdn.example.com/content/seg-003.ts
#EXTINF:6.000,
https://cdn.example.com/content/seg-004.ts
VMAP defines the ad break schedule for a video. SSAI calls the ad server for a VMAP document first, then uses it to determine when and how many ads to request. Without VMAP, the SSAI vendor cannot know where breaks occur in live or VOD content.
<?xml version="1.0" encoding="UTF-8"?>
<vmap:VMAP xmlns:vmap ="http://www.iab.net/vmap-1.0" version ="1.0" >
<!-- Pre-roll: fires at content start (timeOffset="start") -->
<vmap:AdBreak
timeOffset ="start" <!-- Other options: "end", "00:08:00" (HH:MM:SS), or percentage "25%" -->
breakType ="linear" <!-- "linear" = full-screen video; "nonLinear" = overlay; "display" = banner -->
breakId ="preroll-1" >
<vmap:AdSource
id ="pre-source-1"
allowMultipleAds ="false" <!-- false = only first ad in response serves (no pod) -->
followRedirects ="true" > <!-- true = SSAI follows VAST Wrapper chains -->
<vmap:VASTAdData>
<!-- Inline VAST — embed the VAST XML directly in VMAP -->
<VAST version="4.1"> <!-- ...full VAST here... --> </VAST>
</vmap:VASTAdData>
</vmap:AdSource>
<vmap:TrackingEvents>
<vmap:Tracking event ="breakStart" >
<![CDATA[https://track.example.com/vmap/break/pre/start?cb=__random__]]>
</vmap:Tracking>
<vmap:Tracking event ="breakEnd" >
<![CDATA[https://track.example.com/vmap/break/pre/end?cb=__random__]]>
</vmap:Tracking>
</vmap:TrackingEvents>
</vmap:AdBreak>
<!-- Mid-roll: fires at 8 minutes into content -->
<vmap:AdBreak
timeOffset ="00:08:00"
breakType ="linear"
breakId ="midroll-1" >
<vmap:AdSource
id ="mid-source-1"
allowMultipleAds ="true" <!-- true = pod: multiple ads served sequentially in one break -->
followRedirects ="true" >
<vmap:AdTagURI templateType ="vast4" >
<!-- Dynamic VAST tag — SSAI calls this URL to get ad(s) for this break -->
<![CDATA[https://adserver.example.com/vast?pos=mid&break=1&dur=90&cb=__random__]]>
</vmap:AdTagURI>
</vmap:AdSource>
</vmap:AdBreak>
<!-- Second mid-roll: fires at 16 minutes -->
<vmap:AdBreak
timeOffset ="00:16:00"
breakType ="linear"
breakId ="midroll-2" >
<vmap:AdSource id ="mid-source-2" allowMultipleAds ="true" followRedirects ="true" >
<vmap:AdTagURI templateType ="vast4" >
<![CDATA[https://adserver.example.com/vast?pos=mid&break=2&dur=60&cb=__random__]]>
</vmap:AdTagURI>
</vmap:AdSource>
</vmap:AdBreak>
<!-- Post-roll: fires when content ends -->
<vmap:AdBreak
timeOffset ="end"
breakType ="linear"
breakId ="postroll-1" >
<vmap:AdSource id ="post-source-1" allowMultipleAds ="false" followRedirects ="true" >
<vmap:AdTagURI templateType ="vast4" >
<![CDATA[https://adserver.example.com/vast?pos=post&cb=__random__]]>
</vmap:AdTagURI>
</vmap:AdSource>
</vmap:AdBreak>
</vmap:VMAP>
start = pre-roll, end = post-roll, 00:08:00 = absolute timecode, 25% = percent of content duration. For live streams, timecodes should match expected SCTE-35 signal positions.true = the SSAI fills the break with multiple ads sequentially (pod). false = only one ad serves regardless of how many the VAST response contains. Setting this incorrectly causes the break to be either too short or overly long.true = SSAI follows wrapper redirects to reach the InLine creative. false = SSAI only uses first-level VAST, causing empty breaks when ads use wrapper chains (most do).AdTagURI for dynamic ad serving (ad server decides which creative at request time). Use VASTAdData to embed static VAST — useful for fallback or house ads that never change.| Error | Where it appears | Meaning | How to fix |
|---|---|---|---|
| VAST 400 | VAST error URL, SSAI logs | XML parse error — malformed VAST tag | Validate the VAST tag with the IAB validator. Check for unescaped & in tracking URLs. |
| VAST 401 | VAST error URL | VAST schema validation error | Confirm VAST version matches schema. VAST 4.1 tags must not use deprecated VAST 2.0 elements. |
| VAST 301 | Wrapper chain | Wrapper redirect returned no ads | Confirm the wrapped VAST URL is accessible from the SSAI server IP. Check for geo or IP allow-list blocking. |
| VAST 303 | Wrapper chain | Too many wrapper redirects (> 5 is common limit) | Flatten the VAST wrapper chain. Each hop adds latency — more than 3 hops frequently causes timeouts on CTV devices. |
| VAST 101 | SSAI logs / debug tools | VAST schema error — missing required element | Verify <Impression>, <MediaFile>, and <Duration> are all present in the InLine element. |
| 503 from ad server | SSAI network logs | Ad server overloaded or no fill | Check ad server latency. If timeout, increase SSAI ad request timeout. Add backfill VAST fallback in VMAP. |
| Black screen at break | Player / viewer report | Ad segment 404, codec mismatch, or missing discontinuity marker | Inspect manifest in a text editor — confirm segment URLs resolve. Verify TARGETDURATION matches. Check for missing EXT-X-DISCONTINUITY. |
| Beacons not firing | DSP reporting, VAST tracking | SSAI server-side tracking failed or was not configured | Confirm SSAI vendor has server-side tracking enabled for the creative. Check the tracking URL format — CDATA wrapping is required. Test with curl from the SSAI server IP. |
The ad break ends earlier than the VMAP planned-duration. One or more ads in the pod failed to transcode, returned 404, or the VAST response was empty. SSAI fills what it can and returns to content.
▸ Pull SSAI session logs for the break ID. Check how many VAST responses returned creative vs. empty. Confirm each ad creative URL is accessible from the SSAI server’s IP — geo or IP blocks are a common cause.
After returning from an ad break, the audio and video tracks in the content stream are offset. Caused by a discontinuity marker being missing or misplaced at the ad-to-content boundary.
▸ Download the manifest and inspect the second EXT-X-DISCONTINUITY marker. Confirm it is on its own line immediately before the first content segment after the break. If missing, escalate to SSAI vendor with the session ID.
Ad frequency is not capped — same creative appears in multiple ad pods in the same session. SSAI is not passing session ID or user ID to the ad server, so frequency capping cannot function.
▸ Confirm the SSAI session initialization passes a unique user or device ID to the ad server via the VAST request parameters. In GAM, configure frequency capping per session. Use cache-busting macros in AdTagURI.
SSAI fires tracking beacons server-side, but the DSP’s impression tracker is not recording them. The DSP’s impression pixel expects a client-side fire (from the device), not a server-side fire from the SSAI server IP.
▸ Confirm the DSP supports server-side impression tracking. Some DSPs add IP allow-lists for SSAI servers. Share your SSAI vendor’s server IP range with the DSP and ask them to whitelist it.
In live streams, SCTE-35 signals trigger ad breaks. If the live encoder sends a SCTE-35 signal early or late, the ad break starts at the wrong point in the content — cutting off dialogue or starting in the middle of an action sequence.
▸ Capture the SCTE-35 signal from the live encoder using a tool like SCTE-35 Parser. Compare the signal timestamp against the actual program event time. Work with the encoder team to calibrate signal lead time.
Content plays smoothly but the player buffers every time an ad serves. Ad segments are coming from a different CDN than content, with higher latency or lower cache hit rate.
▸ Compare the content segment CDN with the ad segment CDN in the manifest. Confirm the ad CDN has POPs in the same regions as your viewer base. Ask the SSAI vendor to pre-warm the ad CDN cache for high-frequency creatives.
manifest-annotated.txt with line-by-line comments on what each marker does.vmap-pod.xmlssai-error-audit.md with ranked error list and recommended fixes.Enter any two values
to calculate the third
More tools coming soon