Browse the help center

Video behind server-side ad insertion (SSAI)

Serve a video placement through a CTV stitcher — how Epom reads the viewer's IP, device and consent from what the stitcher forwards, which stitcher to pick on the placement so the tag carries its macros, what each macro parameter does, and how to keep a stitcher's data-centre traffic from being banned as fraud.

How-to network manager publisher admin Account → Publishers → site → zone → placement (Video) → Basic info → VAST configuration → Server-side ad insertion (SSAI)

What it is and why

On connected TV most streams do not let the player fetch ads: a stitcher (AWS MediaTailor, Google DAI, SpringServe, FreeWheel, Publica, Brightcove, Yospace…) calls the placement’s VAST tag on behalf of each viewer, splices the creative into the stream and usually fires the tracking pixels from its own servers. Every request therefore arrives from a data centre, and unless the stitcher passes the viewer along, Epom sees one address and one user-agent for thousands of households. This page is about telling Epom which stitcher calls the tag, so the tag asks for the viewer’s details in that vendor’s own macro syntax, and about what the ad server does with what comes back.

Example. A FAST channel runs through SpringServe. With Server-side ad insertion (SSAI) left Off, 40,000 viewers in an evening look like one device in one city, geo targeting matches whatever the stitcher’s data centre resolves to, and frequency capping counts them as one visitor. Set it to SpringServe (Magnite), re-copy the tag, and it ends in clientIp={{IP}}&clientUa={{USER_AGENT}}&ifa={{DEVICE_ID}}…: each request now carries the viewer’s IP, the TV’s user-agent and its advertising ID, so targeting, capping and the bid requests to DSPs see 40,000 TVs.

Typical scenarios

You haveSetThen
A FAST channel sold through SpringServe or PublicaSSAI = SpringServe (Magnite) / PublicaRe-copy the tag; every parameter in the table below is filled by the stitcher, schain included
An AWS MediaTailor or Brightcove streamSSAI = AWS Elemental MediaTailor / Brightcove SSAI (VOD)The stitcher fills the IP (and, for Brightcove, the user-agent); everything else arrives only if your player passes it to the session under the parameter’s own name
Google DAISSAI = Google Ad Manager DAIIP, user-agent, device ID, page URL, break length and the GDPR / US privacy signals are filled; Google has no macro for the content, GPP or the supply chain
A stitcher not on the listSSAI = IAB standard macros, or ask an admin to add the vendor under Upstream macro presetsThe IAB VAST 4 macros fill IP, user-agent, device ID, page URL, break length and consent
A stitcher that reaches you only with headers, no macrosSSAI stays Off; ask the stitcher to send X-Device-IP and X-Device-User-AgentEpom reads the viewer from the headers (table below)
A channel whose inventory partner sells it before youSSAI = the stitcher, and the site’s Seller type = IntermediaryThe chain the stitcher passes in schain reaches buyers with your node appended — see Supply chain setup, scenario 7

Step by step

  1. Open the video placement, expand Basic info → VAST configuration and set Server-side ad insertion (SSAI) to the service that calls this tag. The setting needs no permission of its own — anyone who can edit the placement can change it.

    SSAI picker in the VAST configuration block

  2. Click Save — the setting saves with the rest of Basic info.

  3. Open Ad tags & API → VAST tags. The VAST, VPAID and VMAP tags now end in that vendor’s macros, and under them the Server-side ad insertion (SSAI) macros table explains each one. Entries marked as player-provided (MediaTailor’s [player_params.*], Brightcove’s {{url.*}}) are filled only from what your player passes to the stitcher’s session.

    VAST tags tab with the MediaTailor macros table under the tags

  4. Copy the tag and give it to the stitcher, replacing the one it had: a tag copied before you picked the vendor carries no macros.

  5. Ask the stitcher to forward the viewer’s headers as well — X-Device-IP (or X-Forwarded-For) and X-Device-User-Agent — because macros go into the tag only. The impression, click and VAST event pixels the stitcher fires carry no macros, so for them Epom relies on the headers.

  6. Add the stitcher’s egress ranges as Whitelist rows on the Ban & white-listed IPs tab under Admin → Validation & anti-fraud → Validation settings (Moderation & quality) — or ask your network administrator to. See the fraud caveat below for why.

  7. If the channel’s inventory partner sells it before you, set the site’s Seller type to Intermediary so the chain the stitcher passes in schain is used (scenario 7).

How Epom reads the viewer

The request comes from the stitcher’s data centre, so the viewer’s details are taken from what it forwards, first match wins:

Viewer detailTaken from, first match wins
IP addressclientIp tag parameter → X-Device-IP → X-Forwarded-For (first address) → other proxy headers → the connection
User-AgentclientUa tag parameter → X-Device-User-Agent → the request’s User-Agent
LanguageX-Device-Accept-Language → Accept-Language
Page / app refererX-Device-Referer → Referer → X-Referer

The device user-agent replaces the stitcher’s rather than being combined with it, so device, OS and browser targeting, bot detection and the user-agent sent to programmatic demand all see the TV, not the stitcher. The same rules apply to impression, click and VAST event pixels, so a pixel fired by the stitcher with the viewer’s headers counts against that viewer.

What the creative side of the response carries for a stitcher — a stable creative id, the mezzanine file and event names every VAST version accepts — is described under Video banners. What the slot pays can travel in the answer too: see Pricing in the VAST answer.

Tell Epom which stitcher calls the tag

Headers are not all a stitcher can pass on. Most of them — and ad servers that resell video the same way — also fill macros in the tag URL: the viewer’s IP and device, the app, the show, the ad break and the viewer’s privacy choices. Each vendor has its own macro syntax, and nothing is filled unless the tag asks for it. The Server-side ad insertion (SSAI) setting is what makes the tag ask.

The list is the network’s upstream macro presets, kept by admins under Admin → Upstream Macro Presets — a vendor not on it is a new preset there, not a code change. It ships with these:

SettingWhat it fills
Off (default)nothing — players call the tag directly
IAB standard macrosthe IAB VAST 4 macros: [DEVICEIP], [DEVICEUA], [IFA], [PAGEURL], [BREAKMAXDURATION], [GDPRCONSENT], [GPPSTRING], … [LATLONG] and [REGULATIONS] pack two values into one, so latitude, longitude, gdpr and coppa are not filled; there is no supply chain macro
Google Ad Manager DAI[DEVICEIP], [DEVICEUA] (the only user-agent macro that expands inside a VAST redirect), [IFA], [PAGEURL], [BREAKMAXDURATION], %%GDPR%%, [GDPRCONSENT], %%US_PRIVACY%%, %%TFCD%% — Google has no macro for content, GPP or the supply chain
AWS Elemental MediaTailor[session.client_ip], [session.referer] and [session.avail_duration_secs] from MediaTailor itself; everything else as [player_params.<parameter>], which your player passes. No user-agent macro: MediaTailor forwards the viewer’s User-Agent header
SpringServe (Magnite)every parameter in the table below: {{IP}}, {{USER_AGENT}}, {{DEVICE_ID}}, {{CONTENT_GENRE}}, {{US_PRIVACY}}, {{GPP_STRING}}, {{SCHAIN}}, …
Brightcove SSAI (VOD){{system.ip_address}} and {{system.user_agent}} from Brightcove; everything else as {{url.<parameter>}}, which your player passes
FreeWheel#e{request.clientAddress}, #e{request.userAgent}, #{request.deviceId}, the page and the ad break, and the privacy key-values your player sets (_fw_gdpr, _fw_gdpr_consent, _fw_us_privacy, _fw_is_lat)
Publicaevery parameter in the table below: [IP], [%UA%], [DID], [%CONTENT_GENRE%], [US_PRIVACY], [GPP], [SCHAIN], …

A SpringServe placement’s VAST tag, for example, ends in:

?clientIp={{IP}}&clientUa={{USER_AGENT}}&ifa={{DEVICE_ID}}&ifa_type={{IFA_TYPE}}&lmt={{LMT}}&dnt={{DNT}}
&latitude={{LAT}}&longitude={{LON}}&bundle={{APP_BUNDLE}}&appName={{APP_NAME}}&appStoreUrl={{APP_STORE_URL}}
&requestUrl={{URL}}&o={{DOMAIN}}&maxDuration={{MAXIMUM_DURATION}}&maxPodDuration={{POD_MAX_DUR}}&maxAds={{POD_AD_SLOTS}}
&contentGenre={{CONTENT_GENRE}}&contentRating={{RATING}}&contentLivestream={{CONTENT_LIVESTREAM}}&contentLanguage={{LANGUAGE}}
&gdpr={{GDPR}}&gdprConsentIab={{CONSENT}}&usPrivacy={{US_PRIVACY}}&gpp={{GPP_STRING}}&gppSid={{GPP_SID}}&coppa={{COPPA}}
&schain={{SCHAIN}}

(one line in the real tag). The parameter names are always ours; a preset only says which of the vendor’s macros fills each. What each one carries — the same descriptions the macros table shows:

ParameterWhat the stitcher fills in
clientIpThe viewer’s IP address. Geo targeting, fraud checks and reports use it instead of the stitcher’s own.
clientUaThe viewer’s device user-agent, for device, OS and browser targeting.
ifaThe device advertising ID (Roku RIDA, Samsung TIFA, …), for frequency capping and programmatic demand.
ifa_typeWhich platform issued the ID (rida, tifa, aaid, …). Passed to buyers as device.ext.ifa_type.
lmt1 when the viewer limits ad tracking. Passed to buyers as device.lmt.
dnt1 when the viewer’s device asks not to be tracked. Passed to buyers as device.dnt.
latitude, longitudeDevice coordinates, when the player knows them. Passed to buyers as device.geo.lat / lon (GPS).
bundleThe bundle ID of the app or channel.
appNameThe app or channel name. Passed to buyers as app.name.
appStoreUrlThe app’s store page. Passed to buyers as app.storeurl.
requestUrl, oThe page or content URL and its domain, for domain targeting and reports.
maxDurationThe longest ad the player accepts, in seconds. Buyers get the shorter of it and the placement’s maximum.
maxPodDuration, maxAdsThe ad break’s length in seconds and how many ads it holds, for ad pods.
contentGenre, contentRating, contentLivestream, contentLanguageThe show’s genre, age rating (TV-PG, …), whether it is live (1/0) and its language. Passed to buyers in content.*, and targetable with Custom Parameter targeting.
gdpr, gdprConsentIabWhether GDPR applies and the TCF consent string. Used for the consent check; passed to buyers when the network forwards privacy signals.
usPrivacyThe US Privacy string, such as 1YNN. Passed to buyers when the network forwards privacy signals.
gpp, gppSidThe GPP consent string and the sections that apply. Passed to buyers when the network forwards privacy signals.
coppa1 for child-directed traffic. Passed to buyers when the network forwards privacy signals.
schainThe supply chain of the sellers before you, in the IAB form 1.0,1!asi,sid,hp. Buyers receive it in source.schain with your own node added at the end. Used only on sites whose seller type is Intermediary or Both (see Supply chain setup).

The setting is also on the REST API — ssaiStitcher on /rest-api/placements/update/video takes a preset’s code (SPRINGSERVE, AWS_MEDIATAILOR, …), and GET /rest-api/upstream-macro-presets lists them with their macros — and is copied when the placement is duplicated.

Caveats and exceptions

Player-provided values. MediaTailor’s [player_params.*] and Brightcove’s {{url.*}} are filled only from what your player passes to the stitcher’s session — the table on the VAST tags tab marks them. Pass what your player knows (the device ID, the app, the show, the consent strings) under the parameter’s own name, or it arrives empty.

Your own defaults win. A parameter you already set as a default parameter (say ifa=${IFA}) keeps your value; the stitcher’s macro for it is not added.

A macro the stitcher leaves unfilled is ignored. [DEVICEUA], {{IP}}, [%UA%], #{request.deviceId} and the like arriving literally count as “not sent”, so a stitcher that does not know a macro cannot turn it into one shared fake device — or a fake genre or consent string.

Privacy signals reach buyers only where the network allows it. gdpr, the TCF string, usPrivacy, gpp/gppSid and coppa go into the bid request (regs.* and user.consent for OpenRTB 2.6 partners, regs.ext / user.ext for older ones) only when Pass privacy signals to DSPs is on in the GDPR settings. The other signals always go.

Device IDs. ifa_type (rida, tifa, aaid, …) and lmt are forwarded to programmatic demand as OpenRTB device.ext.ifa_type and device.lmt. An all-zero ID — what a TV hands out once the viewer limits ad tracking — is treated as no ID, so those viewers are not merged into one for frequency capping. Frequency capping on TV traffic needs the server-side subject and a filled ifa: see Frequency capping → Apps, WebViews and connected TV.

Programmatic demand is told the impression is stitched server-side: bid requests from an SSAI placement carry OpenRTB imp.ssai = 3.

Macros go into the tag only, not into tracking pixels: the stitchers above document no substitution inside the VAST they return, so pixels rely on the forwarded headers.

The supply chain on the tag is honoured only on an Intermediary or Both site. On a Publisher site a chain in schain is ignored — nobody sells before you there, and anyone can add a parameter to a tag. MediaTailor and Brightcove fill schain only from what the player passes to the session, and the IAB and Google DAI macro sets have no supply chain macro; without it the request falls back to the chain without upstream nodes.

Warning: a stitcher that forwards no viewer IP sends every viewer’s pixels from a few addresses of its own. Impression-fraud detection then sees one address with thousands of impressions and bans it, and every ad request through that stitcher can then be blocked. Ask the stitcher to send X-Device-IP (or X-Forwarded-For), and add its egress ranges as Whitelist rows on the Ban & white-listed IPs tab under Admin → Validation & anti-fraud → Validation settings — see Moderation & quality.

Not working?

SymptomCheckWhere
Every viewer reports from one city, or geo targeting never matchesThe stitcher passes neither clientIp (a filled macro) nor X-Device-IP / X-Forwarded-ForThe SSAI setting on the placement; the tag the stitcher actually calls (re-copied after the setting?); the stitcher’s header configuration
Device or OS targeting sees a server, not a TVNo clientUa macro and no X-Device-User-Agent; MediaTailor forwards the User-Agent header, Google DAI fills [DEVICEUA]The macros table on the VAST tags tab; the stitcher’s settings
A macro arrives as literal text ({{IP}}, [DEVICEUA])The stitcher does not know that macro — it is treated as not sent, so nothing is faked, but nothing is targeted eitherThe vendor’s macro documentation; pick the right preset or ask an admin to adjust it under Upstream macro presets
A player-provided value is always empty on MediaTailor or BrightcoveThe player does not pass it to the session under the parameter’s own nameThe player’s session parameters; the player-provided badges on the macros table
Requests from the stitcher are blocked, or stopped overnightIts data-centre addresses were banned by impression-fraud detection because no viewer IP was forwardedAdmin → Validation & anti-fraud → Validation settings → Ban & white-listed IPs: whitelist the egress ranges; Moderation & quality
Frequency caps never fire on TVThe cap uses the cookie subject, or ifa is empty or all zeros (limited ad tracking)The cap’s Subject; the ifa the stitcher fills; Frequency capping
Buyers get no consent string or US privacy signalPass privacy signals to DSPs is off for the network, or the vendor has no macro for it (IAB and Google DAI have no GPP)GDPR settings; the preset table above
Buyers see an incomplete chain, or the partner’s node is missingThe site’s Seller type is Publisher, so the tag’s schain is ignored; or the stitcher fills no schain macroThe site’s Supply chain section; scenario 7
Impressions differ from the stitcher’s countPixels fired from the stitcher without viewer headers are judged on the stitcher’s addressAnalytics discrepancies; step 5 above

Reference

ItemValue
SettingServer-side ad insertion (SSAI), Basic info → VAST configuration, default Off; no permission of its own
Shipped presetsIAB standard macros, Google Ad Manager DAI, AWS Elemental MediaTailor, SpringServe (Magnite), Brightcove SSAI (VOD), FreeWheel, Publica; admins add more under Upstream macro presets
RESTssaiStitcher on POST /rest-api/placements/update/video.do (a preset code such as SPRINGSERVE, AWS_MEDIATAILOR); GET /rest-api/upstream-macro-presets lists the presets and their macros; copied on duplicate
Bid requestimp.ssai = 3; device.ext.ifa_type, device.lmt, device.dnt, device.geo.lat/lon, app.name, app.storeurl, content.*, source.schain (your node appended); regs.* / user.consent only with Pass privacy signals to DSPs
Headers readX-Device-IP, X-Forwarded-For, X-Device-User-Agent, X-Device-Accept-Language, X-Device-Referer (table under How Epom reads the viewer)

Related pages

Please disable Adblock
Are you using adblock? Disable it and reload the page to proceed to the destination page.

A registration has already been submitted from this page. Please try again in 10 minutes.