---
title: "Piggybacks reference"
description: "Every type, column, macro and REST call of the Piggybacks card, how the ad server picks which rows fire on a conversion and how sampling is applied — the companion to the Piggyback pixels guide."
type: "reference"
audience: [network-manager, developer]
keywords: [piggyback reference, piggyback types, URL piggyback, JS piggyback, piggyback sampling, fire for, attribution type macro, piggyback macros, piggyback API, which snippet delivers piggybacks, friendly iframe, system action piggyback]
ui_path: "Account → Advertisers → campaign → Piggybacks"
---

Fetch the complete documentation index at: https://epom.com/ad-server/docs/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Piggybacks reference

The companion to [Piggyback pixels](https://epom.com/ad-server/docs/guides/conversions/piggybacks.md). The guide has the steps, the caveats and
the fix-it checklist; this page holds every type, column, macro and REST call, plus how the ad
server chooses which rows fire and how sampling is applied.

## The two types

| Type | How it fires | Validated? |
|---|---|---|
| **URL (server-side)** | The ad server issues the request itself, asynchronously, straight after the conversion is recorded. Nothing runs in the visitor's browser | Yes, where your network's URL-validation switch is off — a malformed URL is refused on save and on inline edit (see [The URL check](#the-url-check) below) |
| **JS code (client-side)** | The markup is returned with the conversion response and runs in the visitor's browser, inside a frame of its own | No — any text is accepted, so a broken tag fails silently in the browser |

Neither type can affect the conversion itself: a URL piggyback's response is logged and
discarded, and a failure is not retried.

### Which type to use

Use **URL (server-side)** whenever the partner accepts a plain call. It does not depend on
the visitor's browser at all, so it also covers app, CTV and server-to-server conversions,
and it needs nothing installed on the conversion page.

Use **JS code (client-side)** when the partner will only credit a hit made *by the visitor* —
Google Ads, GA4, Meta and most partner JS SDKs match on their own third-party cookie and on
the visitor's real browser. A server-side call from the ad server's data centre is, to them,
a request from the wrong device in the wrong country, and is either ignored or attributed
wrongly.

## Which snippets deliver a client-side piggyback

A JS row only runs if the conversion page uses an integration that can receive it, and only two
of the five things **Conversion Tracking Code** hands out can:

| Integration | Delivers JS piggybacks? |
|---|---|
| **Snippet** tab — the landing-page helper | **Yes**, on both its post-click and post-view paths |
| **Pixel Tracking** tab → *JS pixel tracking with piggybacks* | **Yes**. This block only appears when you hold the piggyback permission *and* the campaign's Piggybacks card is on |
| **Pixel Tracking** tab → *JS pixel tracking* | No — it fires a 1×1 image and cannot read a response |
| **Pixel Tracking** tab → *Pixel tracking URL* | No — an image cannot run anything |
| **Postback (S2S)** tab | No — there is no browser involved |

If a partner reports missing client-side hits, check which of these is actually on the
conversion page first. URL rows are unaffected by all of this: the ad server calls them
itself.

The two integrations treat consent differently. The **Snippet** withholds client-side piggybacks
from a visitor who refused consent — the conversion is still counted, but no third-party tag is
asked for. The older *JS pixel tracking with piggybacks* snippet has no such check and delivers
them regardless. The consent it is reading is the one recorded on the publisher's page at
ad-request time, not a consent banner on the conversion page.

## What you can paste into a JS row

Whatever the partner gave you. The row is classified once, on the way out, and always
reaches the browser as working markup:

| What you paste | Read as | What runs |
|---|---|---|
| A tag — `<script>`, `<img>`, `<iframe>`, a comment, several of them | Tag | Exactly as pasted |
| A single URL with no spaces — `https://partner.example.com/px?id=7` | Pixel URL | An `<img>` pointing at it |
| A single URL whose path ends `.js` | Script URL | A `<script src>` pointing at it |
| Anything else — plain JavaScript with no tag around it | JavaScript | The same, wrapped in `<script>` |

`&`, `"`, `<` and `>` are escaped on the way into the tag, so a query parameter like
`&copy=1` reaches the partner as a parameter and not as a `©`. Several JS rows on one
conversion are delivered as one block, one row per line. The dialog's **Will run as** block shows
the badge and the exact markup as you type.

The tag runs in a same-origin frame of its own, 1×1 and positioned off-screen, that stays on the
page after it has run. Inside it `document.referrer` is empty; `top.location` and the `Referer`
header on the tag's own requests are intact; the advertiser's page is `parent.document`; the
advertiser's Content-Security-Policy applies to it.

## Fire for

| Option | Conversions delivered |
|---|---|
| **All conversions** (default) | Every conversion that gets as far as firing piggybacks — post-click, post-view, System, and conversions nothing could classify (Unattributed) |
| **Post-click only** | Only conversions classified **Post-click** |
| **Post-view only** | Only conversions classified **Post-view** |

Narrowing means *only what we positively identified*, not *everything we did not rule out*:
an Unattributed or System conversion reaches an **All conversions** row and no other. Use it
when a partner is paid on one attribution model — a tracker that credits post-click should
not receive post-view hits.

System conversions are the ones the ad server records itself for video and viewability events.
That is what makes a pixel fired *when a banner becomes viewable* possible: pick the viewability
action (**AD VIEWABLE IMPRESSION-GLOBAL (id: N)**) as the **Conversion action**, leave **Fire for**
at **All conversions**, and use a **URL (server-side)** row — a JS row never runs on a viewability
event. The full recipe is in
[Fire a third-party pixel when a banner becomes viewable](https://epom.com/ad-server/docs/concepts/viewability-and-open-measurement.md#fire-a-third-party-pixel-when-a-banner-becomes-viewable).

## Grid columns

| Campaign card | Site and placement cards |
|---|---|
| Type · Code/URL · **Site** · **Placement** · Conversion Action · Attribution · Sampling · Status | Type · Code/URL · **Campaign** · Conversion Action · Attribution · Sampling · Status |

The scoping columns tell you which card you are on, and they scope from opposite sides: a
campaign narrows by **Site** and **Placement**, a site or a placement narrows by
**Campaign**.

**Attribution** shows the row's **Fire for** setting, **Conversion Action** shows
`name (id: N)` or **All**, and the **Sampling** column is absent altogether without the
sampling permission. A placement's card is identical to a site's, down to the columns and
the dialog — see the [conversion tracking overview](https://epom.com/ad-server/docs/guides/conversions.md) for a frame of it in place
alongside the other two placement-level tracking cards.

In-place editors cover **Code/URL**, **Attribution**, **Sampling** and **Status** on all three
cards. Conversion Action, Site, Placement and Campaign are not editable after creation (over
REST, the action of a site or placement piggyback can be changed in place). The **Code/URL**
column shows every value in full: a long URL wraps, a long JS tag scrolls inside its own box.

## How the ad server picks the rows

When a conversion for action *A* is recorded on campaign *C* through placement *P* on site
*S*, the ad server gathers the piggybacks of *C*, *S* and *P* and keeps a row when:

- its scoping matches — a campaign row's Site and Placement, a site or placement row's
  Campaign, each ignored when left at "all"; **and**
- its **Conversion action** is *A* or is **All** — and **All** includes the system actions, so an
  *All* row also fires on every viewability and tracked video event; **and**
- its **Fire for** admits this conversion's attribution type.

Rows that are switched off, and every row on a card that is switched off, are not loaded
into the ad server at all, so they cannot match and cannot affect sampling. A conversion has to
resolve to a campaign, a placement and an action before any piggyback is considered; expired and
rejected conversions return before any piggyback code runs.

## How sampling is applied

Sampling is a percentage from 0 to 100. A value outside that range is refused on every save —
the dialog, the grid and the REST create and update — with an error on **Sampling**.

The two types are sampled differently, and the difference matters when you mix them.

**JS code (client-side)** rows each roll their own dice, after the **Fire for** filter. A
50% row fires on about half the conversions it is eligible for, independently of every other
row.

**URL (server-side)** rows are decided **once per conversion**, from the **lowest** Sampling
among the URL rows that fire for it — rows that matched the scoping *and* whose **Fire for**
admits the conversion. Either every one of those URL rows fires or none does. A row that does
not fire for a conversion has no say in its draw: a 5% **Post-view only** row leaves a 100%
**Post-click only** row on the same campaign firing on every post-click conversion.

URL rows that fire for the same conversions share the lowest Sampling among them, so a 5% row
throttles a 100% row next to it. If you need one partner sampled and another not on the same
conversions, keep them on different levels of the hierarchy that do not overlap, or make the
sampled one a JS row.

That same draw also changes how the conversion is recorded: a conversion whose URL
piggybacks were sampled out is written to the advertiser's statistics only, without the
publisher-side attribution. Sampling is therefore not free — it costs publisher reporting on
the conversions it drops. A conversion that no URL row fires for is never sampled out.

Sampling of 0 never fires; 100 always fires. Someone whose role cannot set sampling does not see
the **Sampling** column, the REST API returns the field as null for them, and editing other fields
keeps the stored sampling value.

## Macros

Both types accept macros, but only the conversion-time subset. **Piggyback macros** in the
dialog footer is the reference list — 50 entries while the network's entity-name macros are on
(the `_NAME` and `_NAME_ESC` entries drop out for a side that has them off), among them:

| Macro | Resolves to |
|---|---|
| `$$ATTRIBUTION_TYPE$$` | `click`, `view`, `system` — or empty for an Unattributed conversion |
| `$$TRANSACTION_ID_STR$$` / `$$TRANSACTION_ID_LONG$$` | The ad request's transaction id, base-36 or numeric |
| `$$CLICK_ID$$` | The click token, the same value as `$$ACTION_DATA$$` |
| `$$GROSS$$` / `$$NET$$` | The conversion's CPA price |
| `$$CUSTOM_PARAM(<key>)$$` | A custom parameter carried through the conversion |
| `$$UUID$$`, `$$COUNTRY$$`, `$$GDPR$$`, `$$GDPR_CONSENT_IAB$$` | User, geo and consent values |
| `$$CAMPAIGN_ID$$`, `$$BANNER_ID$$`, `$$PLACEMENT_ID$$`, `$$ZONE_ID$$`, `$$PUBLISHER_ID$$` | Entity ids, with `_NAME` variants |

Request-derived macros — IP, user agent, referer, device, advertising id, incognito, time
zone — are absent, because a conversion carries no ad request to read them from. A few
deprecated aliases still work without being listed: `$$TRANSACTION_ID$$` is an old spelling of
`$$TRANSACTION_ID_STR$$`, so an older row using it keeps working, but write new rows against
the list.

The macro list also documents `expired_click` and `expired_view` for
`$$ATTRIBUTION_TYPE$$`. Neither can ever reach a piggyback: expired conversions do not fire
one at all.

A macro the ad server recognises but cannot resolve is replaced with nothing; a macro it does not
recognise is left as literal text. See the [Macros reference](https://epom.com/ad-server/docs/reference/macros.md).

## The URL check

The check applies only to **URL (server-side)** rows and follows your network's strict URL
validation, which is on unless Epom support has relaxed it for your network (a server-side
setting). With strict validation **on**: an `http`, `https` or `ftp` scheme (or a custom scheme
Epom has allowed for your network) and a host under a registered top-level domain. Macros survive
it — `$$NAME$$`, `${NAME}`, `{name}`, `{{name}}`, `[name]`, `%%NAME%%` and `__NAME__` — but
reserved test domains do not: `https://x.partner.example/px` is refused as *Invalid Url* where
`https://x.partner.example.com/px` passes. With validation **relaxed**, the console dialog checks
nothing and saves any text, and over REST only the schemes that run script or read local files
(`javascript:`, `data:`, `file:` and the like) are refused. The campaign card's in-place edit of
**Code/URL** additionally checks the address in the browser (*URL must be valid*) whichever way
validation is set. JS rows are never URL-checked.

## Over the REST API

Site and placement piggybacks have the same life cycle over REST as in the grid:
`POST /rest-api/piggyback/{site|placement}.do` creates one, `.../{id}/update.do` changes
`codeUrl`, `active`, `sampling` (0–100), `deliveryAttribution` and the action it fires for
(`actionId`, or `allActions=true`), and `.../{id}/delete.do` removes it. A field you leave
out keeps its value; the type cannot be changed — delete the row and create it again. Every
call, including the `/get.do` list and the on/off switch, needs the piggyback pixel tracking
permission and at least a view share of the site (for a placement, of the placement's site). Things the grid does not let happen are refused
rather than guessed at:

- an unknown `actionId` is an error on `actionId`.
  An action that is not in your action list — neither visible to you across the network nor
  shared with you — gets the same error, worded the same way, so the answer does not reveal
  whether the id exists. The console's piggyback dialogs and grids apply the same rule;
- `allActions=true` on create stores the row for every action of the campaign;
- a site, placement or piggyback id that does not exist answers `404`, and so does a deleted
  site or placement — a deleted site takes no new piggyback, and the list, update and delete of
  a deleted site's or placement's piggybacks answer `404` too;
- a parameter the update does not take (such as `type`) is listed back in `ignoredFields`;
- `POST /rest-api/piggyback/site.do` (and `.../placement.do`, `.../campaign/save.do`) with an `id`
  edits that piggyback of the entity it names rather than creating a second one; an `id` that
  belongs to another entity is refused, not moved.

Campaign piggybacks are the third level: `POST /rest-api/piggyback/campaign/save.do`, the batch
`.../campaign/update.do` and `DELETE .../campaign/{id}.do` need the piggyback pixel tracking
permission and update rights on the campaign, like the site and placement calls.

The `/get.do` list returns the rows even while the site's or placement's piggybacks are
switched off, as the grid does, with `piggybacksEnabled` telling whether they fire.

## Permissions

| Who | What |
|---|---|
| Network manager | The whole feature on campaigns, sites and placements, plus the *JS pixel tracking with piggybacks* block on the Pixel Tracking tab |
| Publisher, advertiser | No Piggybacks card and no piggyback block, unless an administrator adds the feature to the role |
| Sampling | Hidden for every role until an administrator grants it separately |
| Campaign piggyback | *Update* on the campaign; *view* on the site it is scoped to |
| Site or placement piggyback | A *view* share of the site (for a placement, of the placement's site); *view* on the campaign it is scoped to |
| Can view share | On a site or placement the card works in full (switch, Add, inline edits, Delete) while the rest of the page is read-only; on a campaign the grid renders without toolbar or checkboxes, cells and Status locked |

Exact grants: [User roles](https://epom.com/ad-server/docs/guides/admin/users-and-roles.md).

## Related

- [Piggyback pixels](https://epom.com/ad-server/docs/guides/conversions/piggybacks.md)
- [Conversion tracking overview](https://epom.com/ad-server/docs/guides/conversions.md)
- [Create and edit conversion actions](https://epom.com/ad-server/docs/guides/conversions/conversion-actions.md)
- [Get the pixel and S2S tracking code](https://epom.com/ad-server/docs/guides/conversions/s2s-and-pixels.md)
- [Landing-page helper](https://epom.com/ad-server/docs/guides/conversions/landing-page-helper.md)
- [Server-side trackers](https://epom.com/ad-server/docs/guides/conversions/server-side-trackers.md)
- [Macros reference](https://epom.com/ad-server/docs/reference/macros.md)
- [Piggybacks API](https://epom.com/ad-server/docs/api/piggybacks.md)