ABR Streaming
Back to blog
Delivery & Ads

SCTE-35 and VAST: Ad Signaling from Splice to Beacon

How SCTE-35 splice_insert, time_signal, and segmentation descriptors surface in HLS DATERANGE/CUE-OUT and DASH, plus VAST/VMAP ad tags, wrappers, and tracking.

ABR Streaming Team4 min read
SCTE-35VASTVMAPAdvertisingHLS

Ad insertion has two distinct halves that engineers often conflate. SCTE-35 answers "where and when is an ad opportunity?" — it marks the break in the stream. VAST/VMAP answers "what ad plays, and how do I get paid?" — it delivers the creative and the tracking. This post walks the full chain from the splice marker to the impression beacon. For how these plug into client- vs server-side architectures, see SSAI vs CSAI; inspect live signals with the SSAI tool and ad responses with the VAST viewer.

SCTE-35: marking the opportunity

SCTE-35 is the broadcast standard for signaling ad insertion points. It originated in MPEG transport streams, where it rides as a splice_info_section on its own PID. The two command types you'll meet most:

splice_insert

The legacy, explicit command: "splice out now, for this duration." It carries:

  • splice_event_id — identifies the break.
  • out_of_network_indicator1 = leaving the network feed for an ad (CUE-OUT), 0 = returning (CUE-IN).
  • splice_time (PTS) — when the splice happens.
  • break_duration — how long the avail is.

time_signal + segmentation_descriptor

The modern, richer approach. A time_signal provides a PTS, and one or more segmentation_descriptors add semantics via a segmentation_type_id:

  • 0x34Provider Advertisement Start, 0x35 — Provider Ad End
  • 0x36Distributor Advertisement Start, 0x37 — Distributor Ad End
  • 0x30/0x31 — Provider Placement Opportunity Start/End
  • 0x10/0x11 — Program Start/End

The descriptor also carries a segmentation_upid (e.g. an Ad-ID or a UPID type identifying the asset) and a segmentation_duration. This is what lets an ad decision system know what kind of break it is, not just that one exists.

A decoded SCTE-35 message conceptually looks like:

splice_info_section
  splice_command_type = time_signal
  pts_time = 1809000000
  segmentation_descriptor
    segmentation_event_id = 0x4800008e
    segmentation_type_id = 0x34   (Provider Ad Start)
    segmentation_duration = 30s
    segmentation_upid_type = 0x09 (ADI)

You can paste a base64/hex SCTE-35 payload into the SSAI tool to decode the command and descriptors.

How SCTE-35 surfaces in HLS

When that transport stream is packaged to HLS, the SCTE-35 is translated into playlist tags.

CUE-OUT / CUE-IN

The simple, widely supported span markers:

#EXT-X-CUE-OUT:30.000
#EXTINF:6.000,
ad_0.ts
#EXTINF:6.000,
ad_1.ts
...
#EXT-X-CUE-IN

CUE-OUT opens the avail with a duration; CUE-IN closes it. Some packagers also emit #EXT-X-CUE-OUT-CONT lines mid-break so a player joining late knows it's inside an avail.

EXT-X-DATERANGE (carrying raw SCTE-35)

The modern, richer carrier — it preserves the binary SCTE-35 so downstream systems get full fidelity:

#EXT-X-DATERANGE:ID="ad-8e",START-DATE="2026-05-16T12:00:00Z",\
  DURATION=30.0,SCTE35-OUT=0xFC302F000000000000FFF...

SCTE35-OUT / SCTE35-IN hold the splice payload; SCTE35-CMD carries a time_signal. DATERANGE is preferred for new deployments because it doesn't lose the segmentation semantics that CUE-OUT flattens.

How SCTE-35 surfaces in DASH

DASH represents avails two ways:

  • Multi-period — each ad break becomes a distinct <Period> in the MPD, the cleanest model for SSAI.
  • In-band/MPD events — an <EventStream> (in the MPD) or emsg (in-segment) carrying the SCTE-35 signal:
<EventStream schemeIdUri="urn:scte:scte35:2013:xml" timescale="90000">
  <Event presentationTime="1809000000" duration="2700000" id="142">
    <scte35:Signal>
      <scte35:Binary>/DAvAAAAAAAA///wFAVI...</scte35:Binary>
    </scte35:Signal>
  </Event>
</EventStream>

The player or SSAI service decodes the Binary to recover the same splice_info_section it would have in HLS.

VAST: the ad and its tracking

Once you know a break exists, you ask an ad decision server for creatives. It responds with VAST (Video Ad Serving Template) — an IAB XML format. The essentials of a VAST InLine ad:

  • MediaFiles — the actual creative renditions (URLs, codecs, bitrates).
  • Duration — creative length.
  • TrackingEvents — the URLs to fire as playback progresses.
  • VideoClicks — clickthrough and click tracking.
  • Impression — the headline "ad was served" beacon.
<VAST version="4.2">
  <Ad id="1234">
    <InLine>
      <Impression><![CDATA[https://adsrv.example/imp?id=1234]]></Impression>
      <Creatives>
        <Creative>
          <Linear>
            <Duration>00:00:15</Duration>
            <TrackingEvents>
              <Tracking event="start">https://adsrv.example/e?ev=start</Tracking>
              <Tracking event="firstQuartile">https://adsrv.example/e?ev=q1</Tracking>
              <Tracking event="midpoint">https://adsrv.example/e?ev=mid</Tracking>
              <Tracking event="thirdQuartile">https://adsrv.example/e?ev=q3</Tracking>
              <Tracking event="complete">https://adsrv.example/e?ev=done</Tracking>
            </TrackingEvents>
            <MediaFiles>
              <MediaFile type="video/mp4" bitrate="2500" width="1280" height="720">
                <![CDATA[https://cdn.example/creative_720.mp4]]>
              </MediaFile>
            </MediaFiles>
          </Linear>
        </Creative>
      </Creatives>
    </InLine>
  </Ad>
</VAST>

Wrappers and the redirect chain

VAST <Wrapper> ads don't contain a creative — they carry a VASTAdTagURI pointing at another VAST response (often a different ad server / SSP). Players follow the chain until they reach an InLine. Each wrapper adds its own tracking URLs (they all fire) and its own latency, so deep wrapper chains are a common cause of slow or failed ad loads. The spec recommends a redirect limit (commonly ~5) to avoid runaway chains. Unwrap and flatten a chain with the VAST viewer.

VMAP: scheduling multiple breaks

VMAP (Video Multiple Ad Playlist) sits above VAST and schedules breaks along the timeline — pre-roll, mid-rolls at offsets, post-roll — each <AdBreak> referencing a VAST <AdSource>:

<vmap:AdBreak timeOffset="00:10:00.000" breakType="linear" breakId="mid1">
  <vmap:AdSource><vmap:AdTagURI templateType="vast4">
    <![CDATA[https://adsrv.example/vast?break=mid1]]>
  </vmap:AdTagURI></vmap:AdSource>
</vmap:AdBreak>

This is how a single response describes the whole ad schedule for an asset.

The chain end to end

  1. SCTE-35 marks the avail (splice_insert or time_signal + descriptor).
  2. Packaging surfaces it as HLS CUE-OUT/DATERANGE or DASH period/event.
  3. The system requests ads via VMAP/VAST.
  4. Wrappers redirect until an InLine creative is found.
  5. The creative plays; tracking events fire at impression and each quartile.

Takeaways

SCTE-35 is the signal and VAST/VMAP is the fill plus measurement — keep them mentally separate. The standard parts (decoding SCTE-35, surfacing CUE-OUT, parsing VAST) are well-trodden; the real engineering pain is wrapper-chain latency and quartile beacon accuracy. Decode splices in the SSAI tool and flatten ad tags in the VAST viewer.

Try it yourself

Reading about streaming is one thing — pull apart a real manifest, play a stream, and measure its QoE with the toolkit.

Browse all tools