OpenRTB bid response, auction and macros
The bid response fields Epom reads and the ones it ignores, the four stages a returned bid passes before it can serve, second-price clearing, the nine loss reasons a buyer can receive and the auction macros filled in nurl, lurl and adm.
A detail page of the OpenRTB integration reference, which has the two directions, the currency rules and the bid floor chain. This page is what happens once a DSP partner answers: the response fields Epom reads, the stages a bid passes before it can serve, how the auction clears, the loss reasons and the auction macros. The request a partner receives is on OpenRTB bid request fields; the inbound direction on Epom as a seller: the /hb/bid endpoint.
Response: fields Epom reads
Epom parses the standard seatbid[].bid[] structure. Anything not listed here is discarded on
parse — the deserializer ignores unknown fields.
| Response field | Used for |
|---|---|
price | Auction ranking, clearing price, billing |
adm | The served creative (HTML / VAST XML / native JSON) |
nurl | Win notice. The ad server calls it once your bid wins, with the auction macros filled in, and ignores the response: the creative must come in adm. Only an http(s) URL on a public address is called. In header bidding it fires from the page instead, as an impression pixel added to the creative |
lurl | Loss notice, called only when the partner’s Call loss notification URL is on — see Loss reasons. As with nurl, the response is ignored and only an http(s) URL on a public address is called |
impid | Matched against the request’s impression; a bid that matches none is dropped |
crid | Creative-scan verdict key, troubleshooting, reporting |
adid | Fills the ${AUCTION_AD_ID} macro |
cat[] | The creative’s IAB content categories — carried onto the winning creative and written as <Category> into the VAST 4.x response, so a server-side ad insertion stitcher reselling the ad sees them |
adomain[] | Carried onto the winning creative and republished as the adomain of Epom’s own bid when that creative is resold through header bidding — unless the advertiser declares its own Advertiser domain, which wins |
dealid | Validated against the request’s deals |
ext | ext.url and ext.impression_tracking_url, for Epom WL DSP pops |
seatbid[].seat | Deal wseat validation, loss records |
Response id / bidid / cur / customdata | Auction macros and currency handling |
Corner case: several fields you might expect to matter are parsed and then ignored:
cid,attr[]andiurlhave no consumer anywhere in the serving path, so a campaign id sent on the bid does not reach Epom’s reporting. Andw/hare not even modelled — creative size is taken from the request’s impression, never from your bid. Send them for spec compliance, but do not rely on Epom acting on them.
Note on VAST in
adm: the document is taken exactly as you send it. Percent sequences and+inside aVASTAdTagURI, a tracker URL or a click-through survive untouched, so a wrapper pointing at a URL-encoded tag still resolves, and a%%MACRO%%your own stack left behind no longer costs you the impression. Markup that is percent-encoded as a whole — a document that does not start with<— is still decoded first, for partners that send it that way.
Note: when Epom sends a win or loss notice itself, it sends it only to an
httporhttpsURL whose host resolves to a public internet address. A notice addressed anywhere else — another scheme, a private network, loopback or link-local — is skipped the way an unreachable endpoint is, and the auction is unaffected.
Warning: Epom accepts exactly one seatbid containing exactly one bid. A response with two seat objects, or one seat carrying two bids, is discarded whole as an invalid bid response — not trimmed to the best bid. If your bidder normally returns a bid per impression or several candidates per seat, it must be configured to return a single bid to Epom.
What happens to a returned bid
A bid passes through four stages before it can serve. They run in this order:
1. Response validation and floor. The response must be exactly one seatbid with one bid, and
the price must be at or above imp.bidfloor. A bid below the floor is dropped with
BID_WAS_BELOW_AUCTION_FLOOR.
Corner case: a bid carrying a
dealidskips the floor check entirely. Deal bids are held to the deal’s own floor instead, at stage 3. A deal priced below your open-auction floor still clears.
2. Private-auction pre-filter. When any impression in play is a private auction, seat bids whose deals are unsuitable are removed, and a partner left with nothing is dropped.
3. Creative scan. If the network scans the partner’s creatives
(creative scanning), the bid’s creative is checked
before the winner is chosen. A BLOCKED creative is removed from the auction, so the
runner-up wins instead — the impression still fills, just not with the blocked creative. Bids
removed here are recorded as CREATIVE_FILTERED_GENERAL_REASON_UNKNOWN.
4. Deal validation, inside the auction. This runs whenever any bid carries a dealid,
private auction or not:
- a bid whose
dealid+seatmatches no deal on the impression is dropped (INVALID_DEAL_ID); - a bid below its deal’s floor is dropped (
BID_WAS_BELOW_DEAL_FLOOR); - a Bid floor price deal has the bid’s price overwritten with the deal floor, whatever was bid;
- once any private-auction bid is present, every non-private bid is removed.
Corner case: a bid with no
dealidis not dropped by stage 4 — it competes normally unless a private-auction bid displaces it.
Is there a size limit on a bid’s markup or video?
Not in Epom: the ad server sets no cap on the bid response or on adm, and a VAST in adm points
at its video files by URL, so the file size never passes through Epom. A size limit quoted for a
DSP’s video bids comes from the DSP, the player or the upload limit for your own video banners —
not from the auction.
Which imptrackers and eventtrackers of a native bid fire as impression beacons is on the hub:
Native bids: which imptrackers and eventtrackers fire as impression beacons.
Auction and clearing
Epom runs the auction over all surviving bids across all partners for the impression. Second-price clearing applies only when the auction type resolved to second price and more than one bid survived; the clearing price is then:
min( max(deal floor + $0.01, second-highest bid + $0.01), winning bid )
— so it never exceeds what the winner actually bid, and never drops below a deal’s negotiated
minimum. With a single surviving bid the winner clears at its own price, capped toward the floor
plus a cent when the banner is set to Always 2nd price. One cent is 0.01 USD.
The clearing price is what feeds the served markup’s price macro and the impression’s revenue — not
necessarily the raw price the winner sent. See
First vs second price for the full model.
Corner case: the DSP partner’s Require a competing DSP checkbox (and the matching banner-level network parameter) does not change the clearing rule. It withdraws the banner from the auction when it would be the only bidder — the partner is saying “only bid me when there is competition”. A partner that mysteriously never wins on low-competition inventory usually has this set.
Loss reasons
Epom fills ${AUCTION_LOSS} in your lurl with the numeric OpenRTB loss-reason code. Nine of
the specification’s thirty codes are reachable:
| Code | Name | Why Epom sends it |
|---|---|---|
| 0 | BID_WON | You won |
| 3 | INVALID_BID_RESPONSE | Not exactly one seatbid with one bid, or the bid’s impid matched no impression |
| 4 | INVALID_DEAL_ID | dealid + seat matched no deal on the impression |
| 7 | MISSING_MARKUP | The winning bid produced no usable markup |
| 100 | BID_WAS_BELOW_AUCTION_FLOOR | Price under imp.bidfloor (non-deal bids only) |
| 101 | BID_WAS_BELOW_DEAL_FLOOR | Price under the matched deal’s floor |
| 102 | LOST_TO_HIGHER_BID | Ordinary auction loss |
| 103 | LOST_TO_A_BID_FOR_A_PMP_DEAL | Lost to a deal bid |
| 200 | CREATIVE_FILTERED_GENERAL_REASON_UNKNOWN | Removed by the creative scan |
Note:
200is the only signal you get for a creative-scan removal — the loss code does not say why the creative was removed.
Loss notices are sent only when the partner’s Call loss notification URL parameter is enabled.
Auction macros
Epom substitutes these in your nurl, lurl and adm before firing or serving them. An
unresolvable macro is replaced with an empty string, never left in place.
| Macro | Value |
|---|---|
${AUCTION_ID} | The bid request id |
${AUCTION_BID_ID} | The response bidid |
${AUCTION_IMP_ID} | The impid of the impression won |
${AUCTION_SEAT_ID} | The winning seat |
${AUCTION_AD_ID} | The bid’s adid |
${AUCTION_PRICE} | The clearing price, not your bid, in the currency of your bid |
${AUCTION_PRICE:$$ENC$$} | The clearing price, encrypted per the partner’s encryption setting |
${AUCTION_CURRENCY} | The currency of your bid: USD, or your currency |
${AUCTION_CURRENCY:$$ENC$$} | Encrypted form of the same |
${AUCTION_LOSS} | The numeric loss reason — see above |
${AUCTION_MBR} | Market bid ratio, clearing price ÷ bid price, both in your currency, as OpenRTB defines it: 1 under first price, below 1 when a second-price auction clears under the bid |
Note: the value is clearing ÷ bid, never the reciprocal, so it is at most 1. Do not invert it.
Related
- OpenRTB integration reference — the two directions, currency and the bid floor
- OpenRTB bid request fields
- Epom as a seller: the /hb/bid endpoint
- First vs second price — the clearing model in full
- PMP deals
- Configure DSP partners