| Internet-Draft | MSF Ad Creative Signaling | November 2026 |
| Riedl | Expires 5 May 2027 | [Page] |
This document defines the carriage of ad creative signaling -- creative identity, tracking events, and measurement verification metadata as specified by SVTA 2053-1 -- in records on a Media over QUIC (MOQT) Streaming Format (MSF) Event Timeline track. It complements the carriage of SCTE-35 splice signaling over the same mechanism: splice events describe where placement opportunities occur on a media timeline, while the event class defined here describes the creatives that fill them and how their playback is to be measured. This binding is the MSF counterpart of the DASH and HLS carriage bindings defined by SVTA 2053-1, which defines none for MOQT.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://riedlse.github.io/draft-riedl-moq-ad-creative-signaling/draft-riedl-moq-ad-creative-signaling.html. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-riedl-moq-ad-creative-signaling/.¶
Discussion of this document takes place on the Media Over QUIC Working Group mailing list (mailto:moq@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/moq/. Subscribe at https://www.ietf.org/mailman/listinfo/moq/.¶
Source for this draft and an issue tracker can be found at https://github.com/riedlse/draft-riedl-moq-ad-creative-signaling.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 5 May 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
SCTE-35 messages [SCTE35] carried on an MSF Event Timeline track [SCTE35-MOQ] give Media over QUIC deployments the function SCTE-35 was built for: signaling placement opportunities -- break windows and splice conditioning -- on a shared media timeline, using the Event Timeline mechanism of the MOQT Streaming Format (Section 8.1 of [MSF]). What that layer deliberately does not carry is a description of what plays inside those windows.¶
On HLS and DASH, that gap is filled at the manifest: a packager or stitcher decorates each session's playlist with creative identity and tracking metadata. On a one-to-many publish/subscribe transport [MOQT] there is no per-session manifest to decorate -- every subscriber receives the same cached objects -- so anything a client needs at render time (which creative is playing, which tracking resources to request at which quartiles, which verification script to load) must be delivered in band, on the shared timeline, ahead of the splice point.¶
This need is not incidental. Accurate measurement of server-side ad insertion increasingly means client-requested tracking -- per-device accounting at render time rather than server-side firing at publish time, which measures the wrong clock -- and verification means initializing a measurement script [OMSDK] before the slot renders. SVTA 2053-1 [SVTA2053] already standardizes exactly this payload: a Pod/Slot/TrackingEvent/Verification data model (Section 4 of [SVTA2053]), explicitly agnostic to client-side, server-side, and server-guided insertion, with carriage bindings defined per stream format -- DASH EventStream, HLS EXT-X-DATERANGE, HLS Interstitials -- and none for MOQT.¶
This document fills that gap using the structures [MSF] and [SCTE35-MOQ] already put in place: it defines an Event Timeline event class whose records carry SVTA 2053-1 Version 2 carriage envelopes, specifies how those records are timed and correlated with splice signaling, requires that each record carry the distributor's signature so that a subscriber can verify it independently of the relay path, and gives operational guidance for tracking on one-to-many delivery. It changes nothing in the SVTA 2053-1 data model; payload normativity remains with [SVTA2053], and this binding could additionally be published as a carriage section in a future revision of that specification.¶
On status: [MSF] is an adopted work item of the MoQ Working Group, and its Event Timeline mechanism is the substrate here; this document and [SCTE35-MOQ] are individual submissions layered on that substrate, and this one is written to track [MSF] as it evolves under WG change control -- including following its registry, wherever that registry ultimately lands. The event class is deployed: a continuously running server-side ad insertion deployment publishes it, and Section 11 reports what was measured.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses the terms Track, Group, Object, Event Timeline, and
the index reference fields t, l, and m as defined in [MSF], and
the terms Pod, Slot, Tracking Event, Verification, and carriage envelope
as defined in Sections 4 and 5 of [SVTA2053].¶
The index keys are spelled as in the published [MSF]. The MSF
editor's copy has since renamed them T, L and M; this document
will follow whichever spelling MSF settles on, and implementations may
wish to accept both in the meantime.¶
Additionally:¶
The party operating the publisher that emits the media and signaling tracks (typically the party performing ad insertion).¶
A window on the media timeline, typically signaled by SCTE-35, in which advertising content plays.¶
An Event Timeline track carrying SCTE-35 records per [SCTE35-MOQ].¶
A URL whose knowledge alone grants the ability to perform a narrow action, used here as a short-lived, opaque reference to a distributor-operated tracking endpoint [CAP-URLS].¶
An MSF track carrying ad creative signaling:¶
MUST declare a packaging value of "eventtimeline" and a mimeType
of "application/json", as Section 8.2 of [MSF] requires of every
Event Timeline track;¶
MUST declare an eventType value of
"urn:svta:advertising-wg:ad-creative-signaling". This is the data
scheme URI defined in Section 5 of [SVTA2053], used unversioned: as
in the DASH binding, the payload version is carried inside the
payload itself (Section 4.3);¶
MUST declare a depends attribute naming the media track or tracks
whose timeline its records index. When the track accompanies a splice
signaling track, it SHOULD name the same media track(s) as that
track.¶
Because eventType is uniform for a track, and because implementations
do not mix payload flavors within one Event Timeline track (see Section
4.3 of [SCTE35-MOQ] for the same rule applied to SCTE-35), ad
creative signaling is carried on a sibling Event Timeline track
alongside any splice signaling track, not interleaved within it.¶
A splice signaling track is not required. Ad creative signaling describes
what plays inside a placement opportunity; it does not depend on how the
boundaries of that opportunity were signaled. A deployment MAY carry ad
creative signaling with no SCTE-35 records present, leaving break
detection to another mechanism entirely -- in-band timed metadata such as
ID3, an emsg box, or the timing carried by the creative records
themselves. Where a splice signaling track is present, the alignment
rules in Section 5.2 let the two be correlated; where it is absent,
those rules simply do not apply.¶
Each record is a JSON object [RFC8259] following the Event Timeline data format of Section 8.1 of [MSF]. A record MUST contain:¶
exactly one index reference field -- t (wallclock), l (MOQT
Location), or m (media time) -- selected per Section 4.2
and typed as Section 8.1 of [MSF] defines it: t and m are JSON
Numbers of milliseconds (a wallclock index is milliseconds since the
Unix epoch, not a date string), and l is an Array of two Numbers,
the MOQT Group ID and Object ID;¶
a data field whose value is exactly one Version 2 carriage
envelope as defined in Section 4.3; and¶
a sig member holding the distributor's signature over the record,
as Section 4.5 defines.¶
Records are carried in the Objects of the track as Section 8.3 of [MSF] specifies: the first Object of each Group is the independent array of every record accumulated and accessible so far. A subscriber that joins at a Group boundary during a placement opportunity therefore receives its record, provided the publisher keeps the record accessible until the opportunity has ended.¶
Records describing creatives spliced into the media timeline SHOULD use
the m (media time) index, expressed in the coordinate space of the
depends media track and sharing that track's timeline zero point. The
t (wallclock) index MAY be used where a placement opportunity is
defined in wallclock terms and no media time is available. The l
(MOQT Location) index MAY be used for records bound to a delivery
position rather than a media time.¶
When a splice signaling track accompanies this track, records on the two tracks that describe the same point on the media timeline SHOULD carry index values of the same kind, chosen by the same precedence, so that a consumer can correlate them without converting between index types (Section 5.2). The requirement is on the observable result -- comparable indices for co-timed records -- not on adopting any particular document's selection algorithm. A deployment carrying SCTE-35 records per [SCTE35-MOQ] satisfies it by applying that specification's index selection rules (its Section 4.2) to both tracks.¶
The data field carries one SVTA 2053-1 Version 2 carriage envelope
(Section 5.2.2.1 of [SVTA2053]) directly:¶
{ "version": 2, "type": "...", "payload": [ ... ],
"features": { ... } }
¶
The envelope is self-describing -- version and payload type are fields -- so no additional wrapper member is defined.¶
Unlike the DASH and HLS bindings, where bare Version 1 payload objects
are also legal, records in this binding MUST carry Version 2 envelopes
only. On a fan-out transport the envelope's features flags are the
only capability-signaling mechanism available, and the envelope type
field is what allows a uniform track to carry pod, slot,
trackingEvent, and verification payloads interchangeably -- the four
types Section 5.2.2.1 of [SVTA2053] defines.¶
The envelope and its payload objects use the member names and units of
[SVTA2053] unchanged. In particular, every timing member inside the
envelope (start, duration, offset, skipOffset) is a MediaTime: a
number of seconds (Section 4.2.1 of [SVTA2053]), not milliseconds.
Only the record's index reference is in milliseconds, because [MSF]
defines it so.¶
A publisher MUST NOT set to true any features flag that [SVTA2053]
does not define. Under Section 5.2.2.1 of [SVTA2053], an interpreter
that meets a flag set to true that it does not support raises an
error and ignores the payload, so an undefined flag makes every
conforming interpreter discard the record. The only flag defined today is
remoteFields, which [SVTA2053] requires to be true when the payload
uses $remote (Section 7); a payload that does not use
$remote can omit features entirely.¶
This document places no requirement of its own on receivers. A receiver
applies Section 5.2.2.1 of [SVTA2053] unchanged: a flag set to true
that it does not support means it ignores that payload, while a flag set
to false, or an absent features member, asks nothing of it. The
publisher-side rule is needed because on one-to-many delivery the
publisher never learns that a record was discarded (item 6 of
Section 10).¶
Two inconsistencies in [SVTA2053] matter to parsers. Its TrackingEvent
payload table (Table 11) lists start and duration where its object
model (Section 4.4.5) defines offset, and its Remote Fields table
(Table 16) says a $remote.verifications URI resolves to an envelope of
type verifications where the envelope (Table 15) defines
verification. This document follows the object model and the envelope
definition: TrackingEvent timing is offset, and the envelope type is
verification.¶
It is RECOMMENDED that a publisher emit one pod envelope per
placement opportunity. The slot, trackingEvent, and verification
envelopes MAY be used (mirroring the granularity options of the DASH
binding), but deduplication under redelivery is simplest at pod
granularity, and a decomposed record loses the structural link to the
slot it belongs to (see Section 10).¶
Records on this track MUST be subscriber-invariant: no field value may vary between subscribers. Per-session values enter only after delivery, either by client-side macro substitution (Section 6 of [VAST]) against broadcast templates, or by request-time indirection through capability URLs (Section 7). A macro template is conformant precisely because the template is identical for every subscriber; substitution is a post-delivery operation.¶
The DASH and HLS bindings of [SVTA2053] assume a per-session manifest, so payload fields carrying session-scoped tracking values are well-formed in those bindings. That assumption does not hold here: records on a shared Event Timeline track are delivered identically to every subscriber and are relay-cacheable, so a session-scoped field would both leak across sessions and defeat caching.¶
Every record MUST carry a sig member holding a signature by the
distributor over the rest of the record, so that a subscriber can
verify a record's origin and integrity without trusting the relay path
(Section 12.1). The signature is a JWS [RFC7515] in the JWS Compact
Serialization with a detached, unencoded payload [RFC7797]: the
protected header carries "b64": false and "crit": ["b64"], and the
sig value has the form BASE64URL(protected header) ".."
BASE64URL(signature).¶
The JWS Payload is the JSON Canonicalization Scheme [RFC8785]
serialization of the record object with its sig member removed: the
index reference and the data envelope, canonicalized. The canonical
form is the signing input only. The record is transmitted in whatever
serialization the publisher produces, and a relay or library may
re-serialize it -- member order, whitespace, number formatting within
the rules of [RFC8785] -- without invalidating the signature. The
signature is per record rather than per Object because Section 8.3 of [MSF] re-forms the array of accumulated records at every Group; a
per-record signature survives that and lets each record be verified on
its own.¶
The protected header MUST contain alg and kid. Publishers and
verifiers MUST support "alg": "EdDSA" with Ed25519 keys [RFC8032]
[RFC8037]; other asymmetric JWS algorithms MAY be used where a
distributor and its clients agree. The algorithm MUST be an asymmetric
signature: "none" and the MAC algorithms (HS256, HS384, HS512)
MUST NOT be used by publishers and MUST be rejected by verifiers,
because every subscriber holds the verification key, and a key that
verifies a MAC also forges one. Verifiers MUST NOT take keys from
jku, jwk, x5u or x5c header parameters; a record that carries
them is verified against the provisioned key set as if they were
absent.¶
A distributor publishes its verification keys as a JWK Set
[RFC7517], each key carrying the kid its signatures name. How a
client obtains the key set is out of scope for this document; a
deployment typically provisions it with the playback session, alongside
the client identifier of Section 7.1. Keys are rotated by
publishing the new key before any record is signed with it, and
retiring the old key only after every record signed with it has left
the accessible window (Section 4.1). A distributor SHOULD use
a distinct kid per channel, so that a verifier can reject a validly
signed record substituted from another channel.¶
A verifier MUST check that the sig member is present and well formed,
that alg is permitted, that kid names a key in the current set, and
that the signature verifies over the canonical payload. A record that
fails any of these checks MUST be treated as absent for the purposes of
Section 7: no tracking, $remote, verification or
click-through resource it names is requested. Media playback is
unaffected; the record is advisory. A verifier MAY report the failure
to the distributor over a channel established out of band, which gives
the distributor evidence of tampering on paths it does not operate.¶
The signature is subscriber-invariant (Section 4.4) and so remains
relay-cacheable. It costs one 64-byte signature and a short protected
header per record, and one Ed25519 verification per record received.
[VECTORS] contains a signed record with the test key that signed it
and a script that signs and verifies records; the example in
Section 6 omits sig for brevity.¶
A creative signaling record's index values are interpreted in the same
coordinate space, with the same timeline zero point, as the depends
media track -- and therefore the same space as an accompanying splice
signaling track that names the same media track.¶
The record or records describing a placement opportunity SHOULD carry
the same index value as the SCTE-35 record whose splice point opens
that opportunity (the same m, or the same l Location when the
splice is immediate). A pod whose start differs from the splice point
MUST carry an index within the window the splice defines.¶
The publisher SHOULD publish a placement opportunity's creative signaling record(s) at least one full Group ahead of the media Group containing the splice point. Clients render seconds behind delivery, while render-time tracking and verification fire windows are sub-second: the tracking table must already be resident on the device when the first ad frame displays, with no time for a lookup round trip.¶
Within the envelope, timing is container-relative, per Sections 4.4.2
and 4.4.5 of [SVTA2053]: a Slot's start is relative to its Pod, and
a Tracking Event's offset is relative to its container. Only the
record's index reference is absolute. Consequently, redelivery, looped
schedules, and regional timeline shifts never require payload rewrites
-- which matters where relays cache and re-serve objects.¶
When an envelope carries Slot or TrackingEvent objects without their
Pod (a slot or trackingEvent envelope), the record's index stands in
for the missing container: a Slot's start and a Tracking Event's
offset are relative to the record's index. A standalone Slot therefore
normally has start 0, as the per-slot events of the DASH binding do
(Section 6.1.2 of [SVTA2053]).¶
An SCTE-35 record on the splice signaling track (reproduced from Section 5.1 of [SCTE35-MOQ]) announces a splice at media time 480500 ms:¶
{
"m": 480500,
"data": {
"scte35_payload":
"/DAhAAAAAAAAAP/wFAUAAArXf+/+AAAAAH4AARSyAAAAAA=="
}
}
¶
The companion record on the ad creative signaling track, published at
least one Group earlier (Section 5.3), carries the same index.
It describes a 45.045-second pod with two slots: explicit quartile
tracking and a verification resource on slot 1 (no offset on the
quartile events, so timing is driven by event-type semantics per
Section 4.4.5 of [SVTA2053]), and lazily resolved tracking on slot 2
via $remote (Section 7):¶
{
"m": 480500,
"data": {
"version": 2,
"type": "pod",
"payload": [{
"duration": 45.045,
"slots": [
{
"type": "linear",
"start": 0.0,
"duration": 30.03,
"identifiers": [{
"scheme":
"urn:smpte:ul:060E2B34.01040101.01200900.00000000",
"value": "ABCD1234567H"
}],
"tracking": [
{ "type": "impression", "urls": [
"https://beacons.example.net/c/7f3a92/imp"
] },
{ "type": "firstQuartile", "urls": [
"https://beacons.example.net/c/7f3a92/q1"
] },
{ "type": "midpoint", "urls": [
"https://beacons.example.net/c/7f3a92/q2"
] },
{ "type": "thirdQuartile", "urls": [
"https://beacons.example.net/c/7f3a92/q3"
] },
{ "type": "complete", "urls": [
"https://beacons.example.net/c/7f3a92/done"
] }
],
"verifications": [{
"vendor": "vendor.example-omid",
"parameters": "key=value",
"resource":
"https://cdn.vendor.example/omid-verify.js"
}]
},
{
"type": "linear",
"start": 30.03,
"duration": 15.015,
"identifiers": [{
"scheme": "urn:com:example:ads:id",
"value":
"972c79e1-2363-403e-9287-a0fa4323c389"
}],
"$remote": {
"tracking":
"https://beacons.example.net/c/9d41c7/trk"
}
}
],
"tracking": [
{ "type": "podStart", "urls": [
"https://beacons.example.net/c/p8842/start"
] },
{ "type": "podEnd", "urls": [
"https://beacons.example.net/c/p8842/end"
] }
]
}],
"features": { "remoteFields": true }
}
}
¶
The remoteFields flag is set because slot 2 uses $remote. This
record is also published as the worked-example test vector. The
appendix on test vectors (Appendix "JSON Schema and Test Vectors") shows a record captured from the
running deployment.¶
Two patterns satisfy Section 4.4. In the first, the record carries broadcast tracking templates and the client performs macro substitution locally at activation; SVTA 2053-1 payloads carry over from the DASH and HLS bindings unchanged and no distributor infrastructure is required, at the cost of exposing vendor endpoint inventory in shared bytes, inflating every record by the full tracking list, and leaving per-device accounting to the macros alone. The second, described in the remainder of this section, defers resolution to a distributor-operated endpoint. Both are conformant; a publisher chooses per deployment.¶
Section 5.2.2.2.2 of [SVTA2053] already says interpreters SHOULD defer
$remote resolution until the containing object is about to become
active. That deferral maps naturally onto broadcast fan-out, with one
addition: on a shared timeline, the URLs in tracking and $remote
fields SHOULD be short-lived capability URLs [CAP-URLS] referencing a
distributor-operated tracking endpoint (a "beacon proxy"), rather than
raw measurement-vendor URLs.¶
At activation, the client requests the capability URL (or resolves
$remote from it), appending a client identifier (Section 7.1).
The endpoint performs macro substitution per Section 6 of [VAST] (as
Section 5.2.3 of [SVTA2053] provides) and requests the corresponding
vendor resources with the calling device's context. Two properties fall
out for free on one-to-many delivery:¶
Payload size: shared-timeline records stay small even for heavy vendor tracking lists, with late binding after the pod record ships.¶
Privacy and cache-safety: no per-user macro values and no vendor endpoint inventory appear in bytes that every subscriber and relay cache can read.¶
Because every subscriber receives identical records, per-device measurement requires the device to identify itself at request time, not in the payload. A client SHOULD append a pseudonymous, session-scoped client identifier (issued and signed out of band, for example with the playback session) as a query parameter when requesting capability URLs. This mechanism applies to the indirection pattern; under client-side substitution, per-device accounting rests on the macro values alone. The tracking endpoint can then deduplicate on (resource, event, client identifier), count unique reach, and forward at most one request per device per event to vendor resources with that device's context -- preserving per-device measurement semantics without per-device payloads. Raw device identifiers MUST NOT appear in requested URLs.¶
On a shared fixed timeline, every subscriber reaches the same tracking
instant at the same wallclock moment; naive activation-time behavior
synchronizes both the $remote resolution fetches and the tracking
requests of the entire audience. This applies with particular force
under client-side substitution, where no distributor endpoint sits in
the path to absorb the burst. Clients SHOULD resolve $remote
fields within a randomized window ahead of activation (the publication
lead of Section 5.3 provides the margin), and SHOULD apply
bounded random jitter to tracking requests whose measurement semantics
tolerate it, carrying the original event time as data where the
measurement moment must be preserved. Verification-critical requests
with sub-second fire windows are the exception and are expected to be
provisioned for.¶
Everything above is deliberately identical bytes for every subscriber -- that is what makes it relay-cacheable. Server-guided insertion, per-session capability URLs, and entitlement-scoped fields are per-session by definition and cannot ride the shared timeline. The authors believe the standardization target for that layer is a separate per-session insertion plan mechanism: a unicast-scoped metadata track (or equivalent side channel) carrying distributor session metadata -- the session's pod assignment for each shared-timeline placement opportunity, its capability URLs, and policy fields -- which composes with the shared events defined here (the shared timeline announces placement geometry and the broadcast/default pod; the insertion plan overlays the per-session decision).¶
Between those two extremes there is a third scope worth naming, because the choice is not binary. Records can be published per cohort: one track per distinct ad decision, shared by every device that resolves to it. Per-device plans are by definition uncacheable -- one subscriber per track -- which forfeits the fan-out that motivates one-to-many delivery in the first place and reduces to per-session manifest stitching with extra steps. Cohort scope keeps relay fan-out within each cohort and bounds the number of distinct tracks by the number of decisions rather than the number of viewers. Server-guided insertion can therefore be served at whichever of the three scopes the deployment's decisioning actually requires.¶
The event class defined here is unchanged across all three: shared, per-cohort, and per-session differ in the scope of the track carrying the records, not in the records themselves. That is deliberate -- scope is a deployment decision, and this binding should not have to be redefined to follow it.¶
That per-session mechanism is out of scope for this document and deserves its own. It is named here only as a design constraint: the event class defined in this document is intended to be overridden or parameterized by such a layer without being redefined by it.¶
This section is informative; nothing in it changes the requirements above.¶
Ad signaling is expected to grow by function rather than by record
size. Interactivity, click-through handling, and third-party
verification are each better carried on their own sibling Event
Timeline track, with its own eventType, the same depends media
track and the same index space, than as further members of the records
defined here. A client then subscribes only to the functions it
implements, which settles much of the feature-negotiation question for
whole functions (item 6 of Section 10), and each function's
records keep their own size, cadence and failure behavior. The cost is
the missing placement identifier (item 8 of Section 10): once
records describing one placement opportunity are spread across tracks,
a shared index identifies the opportunity but not the slot within it.¶
Separately, [MOQT] today defines properties at Track and Object scope only. A proposal before the MoQ Working Group [MOQT-SUBGROUP-PROPS] would carry properties in the subgroup header, where they travel with every stream that carries the subgroup, including FETCH responses, instead of riding a first Object that a mid-Group joiner, a filter or a FETCH range can miss. If it is adopted, break-level state -- an opportunity identifier, the remaining break duration, the Group at which content resumes -- could be carried in the subgroup headers of the media track itself. That would complement the records defined here rather than replace them: subgroup properties describe the break at the delivery position as it is delivered, while the Event Timeline describes the creatives ahead of the splice.¶
The data model itself is also growing. SVTA is preparing a 3rd edition, [SVTA2053-2], for non-linear (concurrent) ad and editorial presentations such as overlays, squeezebacks and L-shapes. It is specified as an additive extension of the Version 2 carriage envelope (a Viewport data type, a PresentationType enumeration and layout control) and defines carriage for DASH and HLS. This document carries the Version 2 envelope without interpreting the payload objects (Appendix "JSON Schema and Test Vectors"), so payloads using those extensions would ride unchanged on an ad creative signaling track. Carriage over the MSF Event Timeline, as defined here, is a candidate for a later SVTA2053 edition.¶
This section lists issues to be resolved through working group discussion; it is to be removed before publication.¶
Granularity: one pod envelope per placement opportunity (the
RECOMMENDED default), or slot, trackingEvent and verification
envelopes per creative? Redelivery deduplication is simpler at pod
granularity. The deployment described in Section 11 publishes
pod envelopes only and has not needed anything finer.¶
Update semantics for live: pods change close to air. Is a later
record at the same index a full supersede (last-writer-wins), or is
a patch rule needed? [SVTA2053] has no cancel/supersede concept;
SCTE-35 inherits cancellation from its payload semantics.
Implementation experience favors the simple rule. When a splice
fails, the deployed publisher publishes a complete new record with
the same m, dropping the failed slot and moving the later ones to
where they really air, and its player replaces only the events it has
not yet fired. No patch rule has been needed so far.¶
Deduplication and late joiners: the DASH/HLS bindings give each
event a manifest-unique id for dedup on re-encounter; MSF records
carry no id. Is (eventType, index, payload) a sufficient dedup key
under relay redelivery and FETCH catch-up -- and which past records
MUST a mid-break joiner fetch? Experience so far: with one pod
record per opportunity, m alone has served as the record key (a
changed payload at a known m is a supersede), and the player fires
each event at most once per (m, slot, event type). For late joiners
the deployed publisher keeps each record in every independent Object
until 60 s after its pod ends, and re-sends the retained array as a
new Group about every 3 s when nothing has changed, so a subscriber
that starts at the next Group rather than the latest one holds the
timeline within seconds. Whether that re-send deserves a SHOULD is
open.¶
Switching sets: when ladder rungs are separate tracks, does one
creative signaling track depends on all of them? Splice
conditioning can land at slightly different media times per rung;
container-relative interior offsets survive that, but a single m
may not. Sender-side track switching under discussion in the MOQ
Working Group sharpens this: if relays select the delivered rung per
subscriber and switch at Group boundaries, (a) these records must
remain valid for every rung of a switching set, (b) splice
conditioning must keep switching sets coherent at placement
boundaries, and (c) the delivered rendition is known only at the
client, so rendition-level measurement is necessarily
client-observed.¶
That mechanism now has a concrete shape to test against. The
SWITCH_FROM proposal distinguishes an aligned switch, where Group
IDs from the two tracks are compared, from an unaligned switch,
where they are not. The distinction decides this issue directly: a
single m on one signaling track can only address a switching set if
switches are aligned, since an unaligned switch leaves no common
Group-ID frame in which one index means the same instant on every
rung. Under unaligned switching the interior offsets survive but m
does not, which would push the binding toward either a per-rung
signaling track or an index defined against something other than the
media track's Group IDs. The proposal was not resolved at the
2026-09-08 interim; at the 2026-10-14 interim the working group took
the direction of reducing it to the group-aligned mode, with other
modes left to extensions, and a consensus call is pending as of this
revision. At least one implementation (MOQtail) reports partial
draft-18 support, so experience should be available before this
document has to choose.¶
Common-format scope: is the Event Timeline binding the whole
story, or is a container-level mapping (the envelope in CMAF emsg)
also wanted so one payload survives packager transit across
HLS/DASH/MOQT boundaries?¶
Feature negotiation on one-to-many: [SVTA2053] interpreters
SHOULD error on unsupported flagged features -- sensible unicast
behavior, but a broadcast publisher cannot negotiate per subscriber.
Should the catalog advertise which features a track uses so clients
subscribe eyes-open? The features member this rests on is
confirmed: the Version 2 base carriage envelope defines it as an
object of boolean flags, with features.remoteFields as the only
flag so far, and requires that an interpreter encountering a flag it
does not support SHOULD raise an error and ignore the payload. This
revision settles the easy half by forbidding flags that [SVTA2053]
does not define (Section 4.3); the hard half -- a defined flag that
some subscribers do not support -- remains. That rule is written for
unicast, where the publisher learns of the failure; on one-to-many
the publisher never hears, so the burden has to move to subscribe
time. Note also that if the per-function tracks of a split
(Section 9) are published as separate broadcasts rather
than sibling tracks, a subscriber selects the functions it supports
by subscribing to them, which answers much of this for the split case
but not for flagged features within a single track.¶
Record integrity: resolved in this revision in favor of
per-record signing (Section 4.5: a detached JWS over the canonical
record, EdDSA with Ed25519 mandatory to implement; Section 12.1
gives the reasoning). Two questions remain. Key distribution: the
key set is provisioned out of band today; a key, or a key set
location, pinned in the catalog or in a track property would let a
client that found the track by name verify it without a separate
channel, and is a possible [MSF] follow-up. And the sig member is
a binding-level addition to the record structure of Section 8.1 of [MSF]; a record signature defined by [MSF] itself would serve every
Event Timeline event class, and the authors intend to raise it
there. A COSE encoding was considered and set aside because the
records are JSON.¶
No stable placement identifier: [SVTA2053] gives neither the Pod
nor the Slot object an identifier. A Pod carries start, duration,
slots and tracking; a Slot carries type, start, duration,
media, identifiers, tracking, verifications, skipOffset and
clickThrough. MediaReference does have an id, but the
specification states that uniqueness of that value "is desired, but
not required", so it cannot be relied on as a key. The Slot
identifiers array identifies the creative, not the
placement: the same creative may legitimately appear twice in one
pod, and each appearance is measured separately.¶
While an envelope carries a whole pod, correlation is structural and
the absence costs nothing. It only bites once records are decomposed
-- which [SVTA2053] itself already does in two places, the $remote
fields that resolve slots, tracking and verifications
separately, and the HLS Interstitial ASSET-LIST mapping, whose own
note observes that it "separates the Slot objects from the Pod
objects without explicit reference". This document decomposes further
still if the event class is split by function across sibling tracks
(Section 9), because then a tracking or verification record
and the creative record it belongs to arrive on different tracks: a
shared index identifies the placement opportunity, not which slot
within it.¶
Three ways out. Derive a composite key from what already exists --
creative identifiers plus the slot's ordinal position within the
pod, anchored by the shared index -- which needs no change to
[SVTA2053] but is positional and therefore fragile under pod
rewriting. Or request stable id fields on Pod and Slot from the
SVTA Advertising Working Group, which is the clean fix and the one
their ASSET-LIST note already argues for. Or define identifiers in
this document, which is the wrong layer: payload normativity belongs
with [SVTA2053] and this binding should not fork the data model.
The first is what an implementation can do today; the second is what
the ecosystem should have.¶
The deployed player (Section 11) takes the first way, with an
occurrence count in place of the raw ordinal: a slot's key is its
identifiers plus the number of earlier slots in the same pod that
carry the same identifiers. That keeps a superseding record that
drops a slot from shifting the other slots' firing history. It would
still mis-key two appearances of one creative if a rewrite reordered
them.¶
(This section follows [RFC7942] and is to be removed before publication as an RFC.)¶
The author operates a continuously running server-side ad insertion deployment over MoQ: fixed-timeline linear channels, publisher to relay to browser, with ad creatives spliced server-side into one shared broadcast, so every subscriber receives the same media objects -- the condition that motivates this document (Section 1). The relay is the open-source moq-relay, version 0.17.9 as of this revision; the publisher and player are not publicly available. Since 2026-10-22 the deployment publishes the event class this document defines. This section reports what it does, what was measured, and what is not yet implemented. Contact: the author.¶
Publisher, live on both of the deployment's ad-inserted channels since 2026-10-22:¶
Each channel's broadcast carries a sibling Event Timeline track named
ad-creative, beside the deployment's earlier proprietary control
track, which is still published in parallel.¶
Each placement opportunity is one record,
{"m": ..., "data": {"version": 2, "type": "pod", "payload": [...]}},
where m is the media time, in milliseconds on the timeline of the
media track, of the pod's first video frame. Envelopes carry no
features member. Slot identifiers use a proprietary identifier
scheme; tracking URLs are capability URLs (Section 7).¶
Objects follow Section 8.3 of [MSF]: each change starts a new Group whose single Object is the independent array of every retained record, with no incremental Objects. The first Group ID is taken from the wallclock in milliseconds, per the restart rule of Section 6.1 of [MSF], and that first Object is the empty array.¶
A read-only validator subscribes to the track and checks every
Object: a JSON array of records, each with exactly one index field
and a Version 2 envelope whose payload has the [SVTA2053] shape
(member names and types, MediaTime values in seconds, identifiers,
non-empty urls, defined event types, no undefined feature flag). It
runs against the deployment's channels.¶
Added on 2026-10-25:¶
Measured slots: each Slot's start and duration are measured from
the creatives actually spliced, rather than taken from the durations
the ad decision declares.¶
Supersede: when a splice fails, the publisher publishes a new record
with the same m, which drops the failed slot (item 2 of
Section 10).¶
Heartbeat: when nothing has changed for about 3 s, the retained array is re-sent as a new Group, so a late joiner that starts at the next Group receives the records within seconds (item 3 of Section 10).¶
Device beacons: when device beacons are enabled, the browser player
fires tracking requests from the records on its synchronization clock
-- the media time it is presenting, the sync.now() clock of the
open-source <moq-watch> player element -- rather than from the
wallclock less an assumed latency. A late joiner fires podStart on
joining, nothing for the slot already in progress, and full chains for
the slots after it. The player falls back to the earlier control-track
model when that clock is unavailable or no record arrives within 5 s.¶
Measured:¶
On a test rig (15 pods, one creative the importer refuses), every one of the 31 slot boundaries the records claimed matched the splice gaps in the exported media to the millisecond, and the refused creative produced superseding records. The durations the ad decision declared would have been 36 to 350 ms early.¶
Replaying the rig's captured records against a simulated playhead, the player's fires came 0.3 to 5.0 ms after the media time, none early (142 fires at a 4 s playback delay).¶
In an observe-only browser session against the running deployment on
2026-10-24 (requests logged, not sent), one 4-ad pod produced 33
events in order: podStart; then impression, start, the three
quartiles and complete for each ad; then podEnd. For its 25
time-based events, device times trailed the publisher's server-side
tracking by 3.58 to 4.07 s, which is the configured 4 s playback
delay, with at most 8 ms of spread within each ad.¶
Unchanged from the previous revision:¶
Placement opportunities on the live channel detected from inbound in-band SCTE-35, parsed to a break window and duration. The deployment consumes SCTE-35; it does not publish a splice signaling track.¶
Tracking indirection as described in Section 7: a proxy
holds vendor beacon URLs and returns compact per-event capability
URLs, which devices request with their own client identifier, with
per-{uuid, event, client} deduplication so a shared broadcast
yields per-device counts. The deployment converged on it for a
concrete reason: a single impression in this inventory carries
roughly 5 to 15 vendor tracking URLs, so firing them from the client
directly means 5 to 15 device requests per impression, multiplied by
every subscriber to a shared broadcast. A capability URL is one device
request that the proxy fans out server-side, which makes the
device-side cost independent of how many vendors a creative carries
(Section 12.2).¶
An MSF catalog, published automatically by the MoQ implementation in use and verified against an interop profile by a tool in the same codebase.¶
NOT yet implemented:¶
Record signatures (Section 4.5). Records are published without a
sig member as of this revision, and the validator does not check
one. The signing and verification code is the published script
([VECTORS]), not yet the publisher.¶
Catalog listing. The ad-creative track is not yet listed in the MSF
catalog, because the MoQ library in use does not yet emit
eventtimeline entries; that needs an upstream change. Consumers find
the track by name.¶
The publication lead of Section 5.3. Records are published when the break opens, immediately before the pod's first media Object, not a full Group ahead. Devices still hold the record before the splice renders because they present several seconds behind delivery, but the SHOULD is not met.¶
Device requests on the running deployment. The tracking endpoint the deployment publishes is reachable only from the service network, so there the device beacons have been observed but not sent, and the publisher's own server-side requests continue in parallel. A publicly reachable endpoint that satisfies Section 12.3 is the remaining step.¶
slot, trackingEvent and verification envelopes, $remote, and
the Slot members media, verifications, skipOffset and
clickThrough.¶
A splice signaling track, and so the correlation rules of Section 5.2.¶
The IANA registration in Section 13, which this document requests.¶
No independent implementation of this document is known to the author.¶
Tracking URLs move money. A relay path is not end-to-end confidential or authentic by default: each hop has its own transport security, but a relay, a cache, or any party able to rewrite Objects between publisher and subscriber can alter a record, and a tampered creative signaling record turns a large synchronized audience into a request cannon aimed at an arbitrary URL -- amplifying the denial-of-service concerns already noted for splice records (Section 6.3 of [SCTE35-MOQ]) with an attacker-chosen target. The same party can suppress measurement by deleting tracking entries, or misattribute it by substituting a record meant for another placement.¶
This is why Section 4.5 requires every record to carry a public key signature that the subscriber verifies: transport authentication protects a hop, the signature protects the record. The signature covers the whole record -- index reference and envelope -- so deletion, insertion and reordering of tracking entries are detectable, not only changes to URLs. It is asymmetric so that holding the verification key, as every subscriber does, confers no ability to forge; a shared secret would make every subscriber a potential publisher. Deployments MUST additionally authenticate the publication path for this track: an unauthenticated publisher cannot forge a record that verifies, but it can replace or suppress the track, which no signature prevents. Receivers SHOULD restrict tracking requests to URL authorities the distributor has designated; capability URLs at a distributor endpoint make this restriction natural.¶
Verification has limits. A hostile client can skip the check, so the signature is detection and deterrence on the network path, not prevention at the device; enforcement against forged requests belongs at the distributor's tracking endpoint. A signature over content proves origin, not freshness. Replay is bounded by the index reference -- a record is acted on at the media time it names, and the accessible window of Section 4.1 is short -- and substitution is bounded by per-channel keys (Section 4.5); a deployment that needs tighter binding can place further context in the protected header by agreement with its clients. In return, verifying clients give the distributor something a publisher cannot otherwise observe on one-to-many delivery: evidence of tampering on paths it does not operate.¶
Even absent an attacker, activation-synchronized behavior across a broadcast audience is a self-inflicted denial of service; the mitigations in Section 7.2 are accordingly stated as client SHOULDs. Capability URLs bound the blast radius of both cases: devices address the distributor's endpoint, which applies its own policy before any vendor is contacted.¶
Receivers MUST treat record payloads as untrusted input: enforce size
and nesting limits on the JSON envelope, and accept only https URLs
in tracking, $remote, and verification fields. Verification resources
are executable code; clients MUST apply their platform's script-loading
policy and SHOULD load them only for slots that will actually render.¶
By construction, the shared timeline carries no per-user data: records are identical for every subscriber, per-device identity is appended by the client at request time (Section 7.1), and identifiers are pseudonymous and session-scoped. Indirection through capability URLs additionally keeps vendor endpoint inventories and macro-expanded values out of relay caches. Distributor tracking endpoints handle real device context (addresses, user agents) and MUST apply applicable consent and data-protection policy before forwarding it to vendors.¶
This document requests registration of the following entry in the "MSF Event Timeline Types" registry established by Section 14.2 of [MSF]. [SCTE35-MOQ] registers entries in that same registry; this document follows the same pattern.¶
| Event Type | Description | Reference |
|---|---|---|
| urn:svta:advertising-wg:ad-creative-signaling | SVTA 2053-1 ad creative signaling, Version 2 carriage envelope | This document; [SVTA2053] |
Editor's note (to be resolved before publication): the URN is defined and owned by the SVTA Advertising Working Group; this registration is made in coordination with that group.¶
This document deliberately reuses the carriage pattern established by Will Law and Suhas Nandakumar in [SCTE35-MOQ], and is designed to compose with it. Will Law also suggested splitting ad signaling by function across Event Timeline tracks (Section 9). The payload data model is the work of the SVTA Advertising Working Group [SVTA2053].¶
Thanks to Will Law, Suhas Nandakumar, Gwendal Simon and Giovanni Marzot for early review and discussion of this document.¶
A JSON Schema for the record shape and a set of test vectors are published
in the schema/ and vectors/ directories of this document's source
repository [VECTORS].¶
The schema encodes the record requirements of Section 4.1 and
Section 4.3: exactly one index reference field, typed as Section 8.1 of [MSF] requires, and a data member holding one Version 2 envelope that
satisfies the envelope rules of Section 5.2.2.1 of [SVTA2053] --
version 2, one of the four envelope types, a non-empty payload
array, and no features flag set that [SVTA2053] does not define. It
does not validate the payload objects themselves; those are governed by
[SVTA2053], not by this document. It checks the shape of the sig
member of Section 4.5 when one is present but does not require it, so
that it continues to validate the unsigned records the deployment
publishes today (Section 11); verifying a signature is in any case
not a schema check.¶
The vectors come in two sets, and both matter: ten files holding twelve
records that MUST validate, and eleven records that MUST NOT. A validator
that accepts everything passes the first set alone. The valid set covers
each envelope type and each index type, back-to-back placement
opportunities, the worked example of Section 6, one Object captured
from the running deployment, and signed-pod, a record carrying the
sig member of Section 4.5; the Ed25519 test key that signed it is
published beside it under vectors/keys/, with a script under
scripts/ that signs and verifies records. Each rejecting case is otherwise valid, so
it fails for the reason its name claims; together they pin the
constraints most likely to be implemented loosely:¶
| Vector | Rejected because |
|---|---|
two-index-fields
|
carries both m and t; exactly one is allowed |
no-index-field
|
carries none |
string-wallclock-index
|
t is a date string; [MSF] requires a Number |
missing-data
|
index only, no envelope |
v1-bare-payload
|
a bare Version 1 payload object, which the DASH and HLS bindings accept and this one does not (Section 4.3) |
unwrapped-pod
|
a Version 2 Pod object with no envelope around it |
wrong-envelope-version
|
"version": 1 in an otherwise Version 2 envelope |
unknown-envelope-type
|
a type outside pod, slot, trackingEvent and verification
|
empty-payload
|
an empty payload; the envelope requires at least one object |
undefined-feature-flag
|
sets a features flag that [SVTA2053] does not define, so a conforming interpreter discards the record |
malformed-signature
|
a sig member that is not a detached compact JWS (Section 4.5) |
The following record is the first of the two in the captured-object
vector: one Event Timeline Object, captured read-only from the running
deployment (Section 11) on 2026-10-26 and then anonymized. The
creative identifiers, the identifier scheme, and every tracking URL
(scheme, host and path) were replaced with placeholders; everything else
is as published -- the index, the MediaTime values, the slot structure,
and the event types and their order. The slot durations are measured
from the spliced media rather than declared, which is why they are not
round numbers:¶
{
"m": 82005596,
"data": {
"version": 2,
"type": "pod",
"payload": [{
"duration": 60.117,
"slots": [
{
"type": "linear",
"start": 0.0,
"duration": 30.058,
"identifiers": [{ "scheme": "urn:com:example:ads:id",
"value": "example-30s-a" }],
"tracking": [
{ "type": "impression", "urls": [
"https://beacons.example.com/c/d916217e/imp" ] },
{ "type": "start", "urls": [
"https://beacons.example.com/c/d916217e/start" ] },
{ "type": "firstQuartile", "urls": [
"https://beacons.example.com/c/d916217e/q1" ] },
{ "type": "midpoint", "urls": [
"https://beacons.example.com/c/d916217e/q2" ] },
{ "type": "thirdQuartile", "urls": [
"https://beacons.example.com/c/d916217e/q3" ] },
{ "type": "complete", "urls": [
"https://beacons.example.com/c/d916217e/done" ] }
]
},
{
"type": "linear",
"start": 30.058,
"duration": 30.059,
"identifiers": [{ "scheme": "urn:com:example:ads:id",
"value": "example-30s-b" }],
"tracking": [
{ "type": "impression", "urls": [
"https://beacons.example.com/c/2ea7e414/imp" ] },
{ "type": "start", "urls": [
"https://beacons.example.com/c/2ea7e414/start" ] },
{ "type": "firstQuartile", "urls": [
"https://beacons.example.com/c/2ea7e414/q1" ] },
{ "type": "midpoint", "urls": [
"https://beacons.example.com/c/2ea7e414/q2" ] },
{ "type": "thirdQuartile", "urls": [
"https://beacons.example.com/c/2ea7e414/q3" ] },
{ "type": "complete", "urls": [
"https://beacons.example.com/c/2ea7e414/done" ] }
]
}
],
"tracking": [
{ "type": "podStart", "urls": [
"https://beacons.example.com/c/bf847920/start" ] },
{ "type": "podEnd", "urls": [
"https://beacons.example.com/c/bf847920/end" ] }
]
}]
}
}
¶
The urls values are capability URLs (Section 7). No event
carries an offset, so each event's timing follows from its type
(Section 4.4.5 of [SVTA2053]). The second record of the captured
Object, about 100 s later on the same timeline, is a four-slot pod of
15 s creatives.¶
Apart from captured-object, the vectors are conformance examples
authored against this specification. Every vector other than the two
signature vectors was checked two ways:
against the schema, with a JSON Schema draft 2020-12 validator; and
against the record and payload checks of the implementation in
Section 11, the same checks its read-only validator applies to live
tracks, which beyond the record rules verify the [SVTA2053] shape of
each payload (member names and types, MediaTime values in seconds,
required identifiers, non-empty urls, and defined event types). Both
accept every valid vector and reject every invalid one, each for the
reason its name gives. The two signature vectors were checked against
the schema, and signed-pod was verified with the published script,
which also rejects it once any byte of the record changes and accepts
it re-serialized with its members reordered.¶
(To be removed before publication.)¶
SVTA 2053-1 conformance. The appendix example and the test vectors
now use the member names and units of [SVTA2053] (start,
duration, offset, tracking, type; MediaTime in seconds) in
place of invented ones (id, startTime, trackingEvents, event;
milliseconds), and no longer give Pod or Slot an id, which
[SVTA2053] does not define (item 8 of Section 10).¶
Section 4.3: adds the verification envelope type, which
[SVTA2053] defines; states that interior timing is in seconds; forbids
setting features flags that [SVTA2053] does not define (the -00
example set two, which a conforming interpreter would discard), while
leaving receiver behavior to [SVTA2053]; and notes two
inconsistencies in [SVTA2053] that affect parsers.¶
Section 4.1: index values are typed as [MSF] requires (t
and m Numbers, l a two-Number Array; a -00 vector wrote t as a
date string); records map to Objects per Section 8.3 of [MSF].
Section 3 adds the mimeType that [MSF] requires.
Section 5.4 says what interior timing is relative to when an
envelope carries Slot or TrackingEvent objects without their Pod.¶
The [SVTA2053] reference now cites the published SVTA 2053-1, Revision 1.0, by its document title and product page, with the Version 2 model named in an annotation; section references corrected.¶
New informative Section 9: splitting ad signaling by function across Event Timeline tracks, break-level state in MOQT subgroup properties [MOQT-SUBGROUP-PROPS], and the additive non-linear extensions of the 3rd edition [SVTA2053-2].¶
Section 11 rewritten: the event class is now deployed (publisher since 2026-10-22, device-side firing since 2026-10-25), with measured results and an explicit list of what is not yet implemented; Section 1 says so. Section 10 records the implementation experience for items 1, 2, 3, 6 and 8, and the status of item 4 after the 2026-10-14 interim. Item 8, the missing placement identifier, stays open.¶
Appendix "JSON Schema and Test Vectors": the vectors are replaced, the schema checks index types and the envelope rules, and an anonymized record captured from the deployment replaces the invented example.¶
The MSF index-key spelling change in the MSF editor's copy is noted in Section 2.¶
Acknowledgments name the early reviewers.¶
New Section 4.5: every record carries a sig member, a detached JWS
([RFC7515], [RFC7797]) over the [RFC8785] canonical record,
with EdDSA over Ed25519 ([RFC8032], [RFC8037]) mandatory to
implement and verification keys as a JWK Set [RFC7517].
Section 12.1 says why transport authentication alone is not enough
and what a verifier checks; item 7 of Section 10 is resolved
accordingly, with key distribution left open; Section 11 records
that the deployment does not sign yet. The schema admits sig, and
the vectors gain a signed record, its test key, a malformed-signature
case, and a signing script.¶