Header Bidding with Prebid.js
Overview
Header Bidding is a way to send ad requests to multiple exchanges simultaneously rather than in a waterfall (such as the adChain passback system).
Header bidding:
- Chooses an ad with the highest eCPM from one of several exchanges
- Then sends the ad to Kevel's ad serving engines to compete in an auction
- The header bidding ad then competes against other ads in the auction priority, thus allowing the inventory to maximize its eCPM.
Compared to other ad serving tools, Kevel simplifies the header bidding process.
Other ad servers use "price buckets" that collect groups of creatives in $0.10 to $1 increments. One "bucket" must be created for each exchange or bidder at each price point.
In Kevel, you make only one creative for header bidding per advertiser. Then, use the setOverrides
or overrides
functionality when placing a Ados.js or Decision API request. The single header bidding creative can have its eCPM overridden and may compete on behalf of any exchanges from the header bids.
Implementing Header Bidding with Price Override and Prebid.js
Notes:
- Creative templates are required for this
- This example is designed to work with our ados Javascript ad code.
- This will track impressions, but not clicks. Clicks are recorded by the header bidding provider.
The following example uses the popular Prebid.js library to make two exchange requests. The bid response with the highest eCPM is then chosen.
This example is implemented in the Kevel demo account and uses an existing:
- site
- channel
- auction priority
- set of two flights with one creative each
In the example, you have:
Flight 1 = eCPM of $5
Flight 2 = eCPM of $0.01
Each flight's ad then inherits its eCPM from its parent flight. Under normal circumstances, Flight 1 should win every time, because it has a higher eCPM.
However, this does not happen because the creative in Flight 2 will be overridden with the CPM of the winning pre-bid, in this case, from Index Exchange. The pre-bid has a CPM of $10 and will win the auction:
Flight 1 = eCPM of $5
Flight 2 (Overridden from Index) = eCPM of $10
The body of creative in Flight 2 is a Custom Template provided by Prebid.js and selected in Kevel that tells Prebid.js to display it's winning ad. This Custom Template is the bridge between Prebid.js and Kevel.
Setting Up the Example in Your Own Account
- Create a new testing Channel or use a currently existing Channel.
- Create a new Advertiser per bidder (AOL and Index Exchange and Appnexus).
- Create a new Campaign for each Advertiser.
- Create a new Priority. Use the Auction selection algorithm. Set the
Weight
to1
. This is will ensure the ad is chosen first. - Create a new Site.
- Attach to the Channel from step 1.
- Create a new Flight in the "AOL" Campaign.
- Choose the Priority from step 4.
- Set the CPM to $5.
- Add
hb_bidder="aol"
as custom targeting. Use the bidder code per Prebid setup instructions. - Add a creative using 300x250 Medium Rectangle ad size and using the Prebid.js Header Bidding Creative Template.
- Save Flight
- Repeat steps 7 through 12 for Index Exchange and Appnexus
In the "This is where the ad code is" section of the HTML page (found below), replace the networkId, siteId, Flight Ids, and adTypes with the values of your network and the ID of your site and adTypes.
Once your account is setup with the examples and you have specified the values in the example HTML, you should see a ad displayed.
Example Prebid.js HTML
Below is an example HTML page that calls Prebid.js and serves a Kevel ad.
<html>
<head>
<script>
/******************************************************************************
* Boilerplate Adzerk Prebid javascript integration.
******************************************************************************/
window.pbjs = window.pbjs || {};
window.pbjs.que = window.pbjs.que || [];
window.ados = window.ados || {};
window.ados.run = window.ados.run || [];
window.adospb = (function() {
function dbg() {
if (pbjs.logging)
console.log.apply(null, arguments);
}
function filter_bad_bids(bids) {
var PBJS_BAD_BID_STATUS = 2;
var good = [];
for (var i = 0; i < bids.length; i++) {
if (bids[i].getStatusCode() !== PBJS_BAD_BID_STATUS) {
good.push(bids[i]);
}
}
return good;
}
function bestBid(bids) {
var good = filter_bad_bids(bids);
var bid = null;
for (var i = 0; i < good.length; i++) {
dbg("BID:", good[i].cpm, good[i].bidder);
if (good[i].cpm > ((bid || {}).cpm || 0)) bid = good[i];
}
//dbg("BEST BID:", bid.cpm, bid.bidder); //This throws an exception if no bids are returned
return bid;
}
function getAdTypes(placementOpt) {
var adTypes = Object.keys(placementOpt.adTypes);
for (var i = 0; i < adTypes.length; i++)
adTypes[i] = parseInt(adTypes[i]);
return adTypes;
}
function getOverrides(bid, flightId) {
var overrides = {flights: {}};
overrides.flights[flightId] = {ecpm: bid.cpm};
return overrides;
}
function addPlacement(divId, placementOpt, bids) {
var networkId = placementOpt.networkId;
var siteId = placementOpt.siteId;
var adTypes = getAdTypes(placementOpt);
var bid = bestBid(bids);
var flightId = bid ? placementOpt.bids[bid.adserverTargeting.hb_bidder].flightId : null;
var properties = bid ? bid.adserverTargeting : {};
var overrides = bid ? getOverrides(bid, flightId) : {};
return function() {
ados_add_placement(networkId, siteId, divId, adTypes)
.setProperties(properties)
.setOverrides(overrides);
};
}
function getAdUnit(divId, placementOpt) {
var adUnit = {code: divId, bids: [], mediaTypes:{}}; //Add mediaTypes object
for (var mediaType in placementOpt.mediaTypes){ //Populate mediaTypes
adUnit.mediaTypes[mediaType] = placementOpt.mediaTypes[mediaType]
}
for (var bidder in placementOpt.bids)
adUnit.bids.push({bidder: bidder, params: placementOpt.bids[bidder].params});
return adUnit;
}
return function(opts) {
opts = opts || {};
pbjs.que.push(function() {
var PREBID_TIMEOUT = opts.timeout || 300;
var adUnits = [];
var placementOpts = opts.placements;
for (var divId in placementOpts)
adUnits.push(getAdUnit(divId, placementOpts[divId]));
pbjs.addAdUnits(adUnits);
function initAdserver() {
if (pbjs.initAdserverSet) return;
pbjs.initAdserverSet = true;
var bidResponses = pbjs.getBidResponses();
if (opts.domain)
ados.run.push(function() { ados_setDomain(opts.domain) });
for (var divId in placementOpts)
ados.run.push(function() {
pbjs.que.push(addPlacement(divId, placementOpts[divId], (bidResponses[divId]||{bids:[]}).bids));
});
ados.run.push(function() { pbjs.que.push(ados_load) });
}
pbjs.requestBids({bidsBackHandler: function(resp){initAdserver()}});
setTimeout(initAdserver, PREBID_TIMEOUT);
});
};
})();
</script>
<script>
/******************************************************************************
* This is where the ad code is.
******************************************************************************/
adospb({
timeout: 900,
placements: {
azk8677: {
siteId: 682165, // change to your Site Id
networkId: 23, // change to your Network Id
adTypes: {
5: [300, 250]
},
mediaTypes: {
banner: {
sizes: [
[300, 250],
[300, 600]
]
}
},
bids: {
ix: {
flightId: 14839134, //change to your Index Exchange flightId
params: {
id: '1',
siteId: 999990,
size: [300, 250]
}
},
aol: {
flightId: 14839145, //change to your AOL flightId
params: {
placement: '3675026',
network: '9599.1',
}
},
appnexus: {
flightId: 14839133, //change to your AppNexus flightId
params: {
placementId: '13144370'
}
}
}
}
}
});
</script>
<script>
/******************************************************************************
* Boilerplate script to load ados.js asynchronously.
******************************************************************************/
(function() {
var d = document;
var protocol = d.location.protocol == "https:" ? "https" : "http";
var z = d.createElement("script");
z.type = "text/javascript";
z.src = protocol + "://static.adzerk.net/ados.js";
z.async = true;
var s = d.getElementsByTagName("script")[0];
s.parentNode.insertBefore(z,s);
})();
</script>
<script>
/******************************************************************************
* Boilerplate script to load Prebid javascript asynchronously.
******************************************************************************/
(function () {
var d = document;
var pbs = d.createElement("script");
pbs.type = "text/javascript";
pbs.src = 'https://acdn.adnxs.com/prebid/not-for-prod/prebid.js'; //This endpoint for testing only.
var target = d.getElementsByTagName("head")[0];
target.insertBefore(pbs, target.firstChild);
})();
</script>
</head>
<body>
<h2>Prebid + Adzerk Test</h2>
<div id="azk8677"></div>
</body>
</html>
Handling Empty Bids
If your Prebid.js bidders choose not to fill an impression, you must follow the instructions below to prevent blanks.
If Prebid.js makes a request but the bidders return no ads, then by default a blank will serve to the placement and no other ads in Kevel will be eligible to serve for that decision.
To allow a decision to choose another ad in your account to fill the blank, you will need to add extra custom targeting to your Prebid ad:
- In the UI, edit any ads in your account that use the Prebid.js Header Bidding Creative Template.
- In the Custom Targeting box, enter
hb_bidder="aol"
. Use the bidder code per Prebid setup instructions. This setting chooses to display the ad ONLY if a winning bid is present in Prebid.js and will target the correct flight for impression tracking purposes. - Save the ads.
Now, if no bidders bid on the request, the decision will choose to serve an ad from elsewhere in Kevel.
Troubleshooting Prebid.js
You can find troubleshooting steps and tips on the Prebid.js site.
Updated 9 months ago