# Server-side (SSAI)

Source: https://docs.ezoic.com/docs/ctv/ssai/


This guide covers calling the Ezoic VAST tag from a server-side ad insertion (SSAI) service, a stitcher, or any other server-side fetcher. Server-side integrations have two failure modes that do not exist on the device. Read the first two sections before anything else.

## Rule 1: Forward the viewer's IP and user agent

The tag uses the IP and user agent it receives. Your stitcher's requests carry your data centre's values, which destroy geo targeting and most demand. Send the viewer's values on every request:

```
&ua=<viewer's user agent, URL-encoded>&ip=<viewer's IP>
```

Most SSAI platforms expose these as session values or macros. For example, AWS MediaTailor uses `[session.client_ip]` and `[session.user_agent]`. Check your vendor's macro table. For IPv6 sessions, send `ipv6=`.

## Rule 2: Check that your macros expand

The tag drops any parameter whose whole value is an unexpanded macro, such as `[IFA]`, `{{BUNDLE}}` or `%%US_PRIVACY%%`. The request still succeeds, but you lose that signal and the revenue attached to it. You get no error.

So do not judge the integration by whether it returns VAST. Capture a real session request and check that each parameter carries a concrete value. See [Testing and troubleshooting](/docs/ctv/testing/#unexpanded-macros).

## Tag template

Replace the `{{MACRO}}` names with your vendor's macro syntax.

```
https://g.ezoic.net/ezoic/videoad/vast?bundle={{APP_BUNDLE}}&app_name={{APP_NAME}}&storeurl={{APP_STOREURL}}&app_domain=<YOUR_EZOIC_DOMAIN>&adunit=<YOUR_VIDEO_AD_UNIT>&ifa={{DEVICE_IFA}}&ifa_type={{IFA_TYPE}}&ua={{USER_AGENT}}&ip={{DEVICE_IP}}&lmt={{LMT}}&us_privacy={{US_PRIVACY}}&gdpr={{GDPR}}&consent={{GDPR_CONSENT}}&content_id={{CONTENT_ID}}&content_title={{CONTENT_TITLE}}&content_genre={{GENRE}}&content_cat={{IAB_CATEGORY}}&content_len={{CONTENT_DURATION}}&livestream={{IS_LIVE}}&pod={{POD_ID}}&slot={{POD_SLOT}}&minduration={{MIN_DURATION}}&maxduration={{MAX_DURATION}}&devicetype=3&plcmt=1&v=3&w=1920&h=1080&cb={{CACHEBUSTER}}
```

Set these once: `app_domain` (your Ezoic domain), `adunit` (your normal video ad unit name), `devicetype=3`, `plcmt=1`, `ssai` if you declare it, and `v`. Use `v=3` unless your pipeline needs more.

## Declare your SSAI level

Some buyers require this declaration before they buy SSAI inventory. Send `ssai` only when you know the value.

| `ssai` | Meaning |
|---|---|
| `1` | All client-side. Not server-side at all. |
| `2` | Assets stitched server-side, tracking pixels fired client-side. |
| `3` | All server-side, stitching and tracking. |

A full server-side stitcher usually sends `3`. A stitcher that stitches assets while the player fires tracking usually sends `2`. If you are not stitched, omit the parameter.

## Ad pods: sequential single-slot requests

The tag returns one ad per request, with no VMAP. To fill a break with several ads:

1. Request each slot separately.
2. Send the same `pod` for the whole break and an incrementing `slot`, starting at 1. Buyers use these to avoid showing the same creative twice in one break.
3. Use a fresh `cb` for each call.
4. Set `minduration` and `maxduration` per slot if the break has a fixed total length. For a 90-second break, for example, request slots with `maxduration=30`.

## Timeout

The auction budget defaults to 3500 ms. If your stitcher has a tighter per-tag timeout, send `tmax` with your real budget in milliseconds so the auction finishes inside your window. The tag keeps a 50 ms network buffer, so a request that ends early can still get a fill if the auction is quick. Set `tmax` to what your stitcher really allows, and test that value end to end.

## Response handling

All three response shapes return HTTP 200, after following any 302:

- VAST with an `<Ad>` element: stitch it.
- Empty VAST document: no fill. Skip the slot, or move down your waterfall.

A response can contain more than one `<Ad>`. If your stitcher takes only one, use the first. The others are same-request fallbacks, not a pod.

## Identity notes

- `ifa` and `ifa_type` come from the device session, passed through your stitcher's session macros. If they are missing, buyers discount the request, as they would on the device.
- If you send universal IDs such as UID2 or RampID, pass them in `eids`. Keep to the size and shape limits in [Parameters](/docs/ctv/parameters/#identity-enrichment).
- Send `schain` only when there are intermediaries between the content owner and Ezoic. Serialize it per the IAB spec exactly, because a malformed chain is dropped.

## Go-live checklist

- [ ] Viewer `ip` and `ua` are forwarded from session values. Check they are not your server's values.
- [ ] Every macro expands with a real session. No literal `[...]` or `{{...}}` reaches the tag.
- [ ] `adunit`, `app_domain`, `devicetype=3` and `plcmt=1` are set.
- [ ] `ssai` is declared, or omitted when you are not stitched.
- [ ] `pod` and `slot` are sent on multi-ad breaks, with a fresh `cb` per call.
- [ ] `tmax` fits inside your stitcher's per-tag timeout.
- [ ] `v` matches what your transcoding pipeline accepts.

