At a high level, a bidder adapter is responsible for:
This page has instructions for writing your own bidder adapter. The instructions here try to walk you through some of the code you’ll need to write for your adapter. When in doubt, use the working adapters in the GitHub repo for reference.
In order to provide a fast and safe header bidding environment for publishers, the Prebid.org team reviews all adapters for the following required conventions:
buildRequests()
and getUserSyncs()
or are included in a winning and rendered creative.$$PREBID_GLOBAL$$
variable: Instead, they must load any necessary functions and call them directly.googletag.pubads().getSlots()
to modify or set targeting directly on slots is not permitted.Failure to follow any of the above conventions could lead to delays in approving your adapter for inclusion in Prebid.js.
Pull requests for non-1.0 compatible adapters will not be reviewed or accepted on the legacy branch.
Prebid.org does not support any version of Prebid.js prior to version 1.0.
With each adapter submission, there are two files required to be in the pull request:
modules/exampleBidAdapter.js
: the file containing the code for the adaptermodules/exampleBidAdapter.md
: a markdown file containing key information about the adapter:
Example markdown file:
# Overview
```
Module Name: Example Bidder Adapter
Module Type: Bidder Adapter
Maintainer: prebid@example.com
```
# Description
Module that connects to Example's demand sources
# Test Parameters
```
var adUnits = [
{
code: 'test-div',
mediaTypes: {
banner: {
sizes: [[300, 250]], // a display size
}
},
bids: [
{
bidder: "example",
params: {
placement: '12345'
}
}
]
},{
code: 'test-div',
mediaTypes: {
banner: {
sizes: [[320, 50]], // a mobile size
}
},
bids: [
{
bidder: "example",
params: {
placement: 67890
}
}
]
}
];
```
The parameters of your ad request will be stored in the ad unit’s bid.params
object. You can include tag info, ad size, keywords, and other data such as video parameters.
For more information about the kinds of information that can be passed using these parameters, see the example below, as well as the existing bidder parameters.
{
var adUnits = [{
code: "top-med-rect",
mediaTypes: {
banner: {
sizes: [
[300, 250],
[300, 600]
]
}
},
bids: [{
bidder: "example",
// params is custom to the bidder adapter and will be
// passed through from the configuration as is.
params: {
unit: '3242432',
pgid: '123124',
custom: {
other: "xyz"
}
},
}]
}];
If you’re the type that likes to skip to the answer instead of going through a tutorial, see the Full Bid Adapter Example below.
The new code will reside under the modules
directory with the name of the bidder suffixed by ‘BidAdapter’, e.g., exampleBidAdapter.js
.
Compared to previous versions of Prebid, the new BaseAdapter
model saves the adapter from having to make the AJAX call and provides consistency in how adapters are structured. Instead of a single entry point, the BaseAdapter
approach defines the following entry points:
isBidRequestValid
- Verify the the AdUnits.bids
, respond with true
(valid) or false
(invalid).buildRequests
- Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid()
test.interpretResponse
- Parse the response and generate one or more bid objects.getUserSyncs
- If the publisher allows user-sync activity, the platform will call this function and the adapter may register pixels and/or iframe user syncs. For more information, see Registering User Syncs below.onTimeout
- If the adapter timed out for an auction, the platform will call this function and the adapter may register timeout. For more information, see Registering User Syncs below.A high level example of the structure:
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
import { config } from 'src/config';
const BIDDER_CODE = 'example';
export const spec = {
code: BIDDER_CODE,
aliases: ['ex'], // short code
isBidRequestValid: function(bid) {},
buildRequests: function(validBidRequests[], bidderRequest) {},
interpretResponse: function(serverResponse, request) {},
getUserSyncs: function(syncOptions, serverResponses) {},
onTimeout: function(timeoutData) {},
onBidWon: function(bid) {},
onSetTargeting: function(bid) {}
}
registerBidder(spec);
When the page asks Prebid.js for bids, your module’s buildRequests
function will be executed and passed two parameters:
validBidRequests[]
- An array of bidRequest objects, one for each AdUnit that your module is involved in. This array has been processed for special features like sizeConfig, so it’s the list that you should be looping through.bidderRequest
- The master bidRequest object. This object is useful because it carries a couple of bid parameters that are global to all the bids.buildRequests: function(validBidRequests, bidderRequest) {
...
return ServerRequestObjects;
}
Building the request will use data from several places:
validBidRequests[]
.bidderRequest
object.Here is a sample array entry for validBidRequests[]
:
[{
adUnitCode: "test-div"
auctionId: "b06c5141-fe8f-4cdf-9d7d-54415490a917"
bidId: "22c4871113f461"
bidder: "rubicon"
bidderRequestId: "15246a574e859f"
bidRequestsCount: 1
bidderRequestsCount: 1
bidderWinsCount: 0
mediaTypes: {banner: {...}}
params: {...}
src: "client"
transactionId: "54a58774-7a41-494e-9aaf-fa7b79164f0c"
}]
Retrieve your bid parameters from the params
object.
Other notes:
requestBids()
, but same across bidders. This is the ID that enables DSPs to recognize the same impression coming in from different supply sources.requestBids()
has been called for this ad unit.requestBids()
has been called for this ad unit and bidder.Here is a sample bidderRequest object:
{
auctionId: "b06c5141-fe8f-4cdf-9d7d-54415490a917"
auctionStart: 1579746300522
bidderCode: "myBidderCode"
bidderRequestId: "15246a574e859f"
userId: {...}
schain: {...}
bids: [{...}]
gdprConsent: {consentString: "BOtmiBKOtmiBKABABAENAFAAAAACeAAA", vendorData: {...}, gdprApplies: true}
refererInfo:
canonicalUrl: undefined
numIframes: 0
reachedTop: true
referer: "http://mypage?pbjs_debug=true"
}
Notes on parameters in the bidderRequest object:
requestBids()
, but is the same across ad units.There are a number of important values that a publisher can set in the page that your bid adapter may need to take into account:
Value | Description | Example |
---|---|---|
Ad Server Currency | If your endpoint supports responding in different currencies, read this value. | config.getConfig(‘currency.adServerCurrency’) |
Publisher Domain | The page may declare its domain, useful in cross-iframe scenarios. | config.getConfig(‘publisherDomain’) |
Bidder Timeout | Use if your endpoint needs to know how long the page is allowing the auction to run. | config.getConfig(‘bidderTimeout’); |
COPPA | If your endpoint supports the Child Online Privacy Protection Act, you should read this value. | config.getConfig(‘coppa’); |
First Party Data | The publisher may provide first party data (e.g. page type). | config.getConfig(‘fpd’); |
Referrer information should be passed to your endpoint in contexts where the original page referrer isn’t available directly to the adapter. Use the bidderRequest.refererInfo
property to pass in referrer information. This property contains the following parameters:
referer
: a string containing the detected top-level URL.reachedTop
: a boolean specifying whether Prebid was able to walk up to the top window.numIframes
: the number of iFrames.stack
: a string of comma-separated URLs of all origins.canonicalUrl
: a string containing the canonical (search engine friendly) URL defined in top-most window.The URL returned by refererInfo
is in raw format. We recommend encoding the URL before adding it to the request payload to ensure it will be sent and interpreted correctly.
You shouldn’t call your bid endpoint directly. Rather, the end result of your buildRequests function is one or more ServerRequest objects. These objects have this structure:
Attribute | Type | Description | Example Value |
---|---|---|---|
method |
String | Which HTTP method should be used. | POST |
url |
String | The endpoint for the request. | "https://bids.example.com" |
data |
String or Object | Data to be sent in the POST request. Objects will be sent as JSON. |
Here’s a sample block of code returning a ServerRequest object:
return {
method: 'POST',
url: URL,
data: payloadObject
};
The interpretResponse
function will be called when the browser has received the response from your server. The function will parse the response and create a bidResponse object containing one or more bids. The adapter should indicate no valid bids by returning an empty array. An example showing a single bid:
// if the bid response was empty or an error, return []
// otherwise parse the response and return a bidResponses array
// The response body and headers can be retrieved like this:
//
// const serverBody = serverResponse.body;
// const headerValue = serverResponse.headers.get('some-response-header')
const bidResponses = [];
const bidResponse = {
requestId: BID_ID,
cpm: CPM,
width: WIDTH,
height: HEIGHT,
creativeId: CREATIVE_ID,
dealId: DEAL_ID,
currency: CURRENCY,
netRevenue: true,
ttl: TIME_TO_LIVE,
ad: CREATIVE_BODY
};
bidResponses.push(bidResponse);
return bidResponses;
The parameters of the bidObject
are:
Key | Scope | Description | Example |
---|---|---|---|
requestId |
Required | The bid ID that was sent to spec.buildRequests as bidRequests[].bidId . Used to tie this bid back to the request. |
12345 |
cpm |
Required | The bid price. We recommend the most granular price a bidder can provide | 3.5764 |
width |
Required | The width of the returned creative. For video, this is the player width. | 300 |
height |
Required | The height of the returned creative. For video, this is the player height. | 250 |
ad |
Required | The creative payload of the returned bid. | "<html><h3>I am an ad</h3></html>" |
ttl |
Required | Time-to-Live - how long (in seconds) Prebid can use this bid. See the FAQ entry for more info. | 360 |
creativeId |
Required | A bidder-specific unique code that supports tracing the ad creative back to the source. | "123abc" |
netRevenue |
Required | Boolean defining whether the bid is Net or Gross. The value true is Net. Bidders responding with Gross-price bids should set this to false. |
false |
currency |
Required | 3-letter ISO 4217 code defining the currency of the bid. | "EUR" |
vastUrl |
Either this or vastXml required for video |
URL where the VAST document can be retrieved when ready for display. | "https://vid.example.com/9876 |
vastImpUrl |
Optional; only usable with vastUrl and requires prebid cache to be enabled |
An impression tracking URL to serve with video Ad | "https://vid.exmpale.com/imp/134" |
vastXml |
Either this or vastUrl required for video |
XML for VAST document to be cached for later retrieval. | <VAST version="3.0">... |
dealId |
Optional | Deal ID | "123abc" |
All user ID sync activity should be done using the getUserSyncs
callback of the BaseAdapter
model.
Given an array of all the responses from the server, getUserSyncs
is used to determine which user syncs should occur. The order of syncs in the serverResponses
array matters. The most important ones should come first, since publishers may limit how many are dropped on their page.
See below for an example implementation. For more examples, search for getUserSyncs
in the modules directory in the repo.
{
getUserSyncs: function(syncOptions, serverResponses) {
const syncs = []
if (syncOptions.iframeEnabled) {
syncs.push({
type: 'iframe',
url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html'
});
}
if (syncOptions.pixelEnabled && serverResponses.length > 0) {
syncs.push({
type: 'image',
url: serverResponses[0].body.userSync.url
});
}
return syncs;
}
}
The onTimeout
function will be called when an adpater timed out for an auction. Adapter can fire a ajax or pixel call to register a timeout at thier end.
Sample data received to this function:
{
"bidder": "example",
"bidId": "51ef8751f9aead",
"params": {
...
},
"adUnitCode": "div-gpt-ad-1460505748561-0",
"timeout": 3000,
"auctionId": "18fd8b8b0bd757"
}
The onBidWon
function will be called when a bid from the adapter won the auction.
Sample data received by this function:
{
"bidder": "example",
"width": 300,
"height": 250,
"adId": "330a22bdea4cac",
"mediaType": "banner",
"cpm": 0.28
"ad": "...",
"requestId": "418b37f85e772c",
"adUnitCode": "div-gpt-ad-1460505748561-0",
"size": "350x250",
"adserverTargeting": {
"hb_bidder": "example",
"hb_adid": "330a22bdea4cac",
"hb_pb": "0.20",
"hb_size": "350x250"
}
}
The onSetTargeting
function will be called when the adserver targeting has been set for a bid from the adapter.
Sample data received by this function:
{
"bidder": "example",
"width": 300,
"height": 250,
"adId": "330a22bdea4cac",
"mediaType": "banner",
"cpm": 0.28,
"ad": "...",
"requestId": "418b37f85e772c",
"adUnitCode": "div-gpt-ad-1460505748561-0",
"size": "350x250",
"adserverTargeting": {
"hb_bidder": "example",
"hb_adid": "330a22bdea4cac",
"hb_pb": "0.20",
"hb_size": "350x250"
}
}
Follow the steps in this section to ensure that your adapter properly supports video.
Add the supportedMediaTypes
argument to the spec object, and make sure video
is in the list:
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: ['video'],
...
}
If your adapter supports banner and video media types, make sure to include 'banner'
in the supportedMediaTypes
array as well
Video parameters are often passed in from the ad unit in a video
object.
The design of these parameters may vary depending on what your server-side bidder accepts. If possible, we recommend using the video parameters in the OpenRTB specification.
For examples of video parameters accepted by different adapters, see the list of bidders with video demand.
Video ad units have a publisher-defined video context, which can be either 'instream'
or 'outstream'
or 'adpod'
. Video demand partners can choose to ingest this signal for targeting purposes. For example, the ad unit shown below has the outstream video context:
...
mediaTypes: {
video: {
context: 'outstream'
},
},
...
You can check for the video context in your adapter as shown below, and then modify your response as needed:
const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video');
const context = utils.deepAccess(bid, 'mediaTypes.video.context');
if (bid.mediaType === 'video' || (videoMediaType && context !== 'outstream')) {
/* Do something here. */
}
Following is Prebid’s way to setup bid request for long-form, apadters are free to choose their own approach.
Prebid now accepts multiple bid responses for a single bidRequest.bids
object. For each Ad pod Prebid expects you to send back n bid responses. It is up to you how bid responses are returned. Prebid’s recommendation is that you expand an Ad pod placement into a set of request objects according to the total adpod duration and the range of duration seconds. It also depends on your endpoint as well how you may want to create your request for long-form. Appnexus adapter follows below algorithm to expand its placement.
AdUnit config
{
...
adPodDuration: 300,
durationRangeSec: [15, 30]
...
}
Algorithm
# of placements = adPodDuration / MIN_VALUE(durationRangeSec)
Each placement set max duration:
placement.video.maxduration = MAX_VALUE(durationRangeSec)
Example:
# of placements : 300 / 15 = 20.
placement.video.maxduration = 30 (all placements the same)
Your endpoint responds with:
10 bids with 30 seconds duration
10 bids with 15 seconds duration
In Use case 1, you are asking endpoint to respond with 20 bids between min duration 0 and max duration 30 seconds. If you get bids with duration which does not match duration in durationRangeSec
array, Prebid will evaluate the bid’s duration and will match into the appropriate duration bucket by using a rounding-type logic. This new duration will be used in sending bids to Ad server.
Prebid creates virtual duration buckets based on durationRangeSec
value. Prebid will
durationRangeSec
was [5, 15, 30] -> 2s is rounded to 5s; 17s is rounded back to 15s; 18s is rounded up to 30s)Prebid will set the rounded duration value in the bid.video.durationBucket
field for accepted bids
AdUnit config
{
...
adPodDuration: 300,
durationRangeSec: [15, 30],
requireExactDuration: true
...
}
Algorithm
# of placements = MAX_VALUE(adPodDuration/MIN_VALUE(allowedDurationsSec), durationRangeSec.length)
Each placement:
placement.video.minduration = durationRangeSec[i]
placement.video.maxduration = durationRangeSec[i]
Example:
# of placements : MAX_VALUE( (300 / 15 = 20), 2) == 20
20 / 2 = 10 placements:
placement.video.minduration = 15
placement.video.maxduration = 15
20 / 2 = 10 placements:
placement.video.minduration = 30
placement.video.maxduration = 30
Your endpoint responds with:
10 bids with 30 seconds duration
10 bids with 15 seconds duration
In Use case 2 requireExactDuration
is set to true and hence Prebid will only select bids that exactly match duration in durationRangeSec
(don’t round at all).
In both use cases, adapter is requesting bid responses for 20 placements in one single http request. You can split these into chunks depending on your endpoint’s capacity.
Adapter must add following new properties to bid response
{
meta: {
iabSubCatId: '<iab sub category>', // only needed if you want to ensure competitive separation
},
video: {
context: 'adpod',
durationSeconds: 30
}
}
Appnexus Adapter uses above explained approach. You can refer here
Adapter must return one IAB accepted subcategories (links to MS Excel file) if they want to support competitive separation. These IAB sub categories will be converted to Ad server industry/group. If adapter is returning their own proprietary categroy, it is the responsibility of the adapter to convert their categories into IAB accepted subcategories (links to MS Excel file).
If the demand partner is going to use Prebid API for this process, their adapter will need to include the getMappingFileInfo
function in their spec file. Prebid core will use the information returned from the function to preload the mapping file in local storage and update on the specified refresh cycle.
Params
Key | Scope | Description | Example |
---|---|---|---|
url |
Required | The URL to the mapping file. | "//example.com/mapping.json" |
refreshInDays |
Optional | A number representing the number of days before the mapping values are updated. Default value is 1 | 7 |
localStorageKey |
Optional | A unique key to store the mapping file in local storage. Default value is bidder code. | "uniqueKey" |
Example
getMappingFileInfo: function() {
return {
url: '<mappingFileURL>',
refreshInDays: 7
localStorageKey: '<uniqueCode>'
}
}
The mapping file is stored locally to expedite category conversion. Depending on the size of the adpod each adapter could have 20-30 bids. Storing the mapping file locally will prevent HTTP calls being made for each category conversion.
To get the subcategory to use, call this function, which needs to be imported from the bidderFactory
.
getIabSubCategory(bidderCode, pCategory)
Params
Key | Scope | Description | Example |
---|---|---|---|
bidderCode |
Required | BIDDER_CODE defined in spec. | "sample-biddercode" |
pCategory |
Required | Proprietary category returned in bid response | "sample-category" |
Example
import { getIabSubCategory } from '../src/adapters/bidderFactory';
let iabSubCatId = getIabSubCategory(bidderCode, pCategory)
As described in Show Outstream Video Ads, for an ad unit to play outstream ads, a “renderer” is required. A renderer is the client-side code (usually a combination of JavaScript, HTML, and CSS) responsible for displaying a creative on a page. A renderer must provide a player environment capable of playing a video creative (most commonly an XML document).
If possible, we recommend that publishers associate a renderer with their outstream video ad units. By doing so, all video-enabled demand partners will be able to participate in the auction, regardless of whether a given demand partner provides a renderer on its bid responses. Prebid.js will always invoke a publisher-defined renderer on a given ad unit.
However, if the publisher does not define a renderer, you will need to return a renderer with your bid response if you want to participate in the auction for outstream ad unit.
The returned VAST URL or raw VAST XML should be added into bid.vastUrl
or bid.vastXml
, respectively.
For example:
function createBid(status, reqBid, response) {
let bid = bidfactory.createBid(status, reqBid);
if (response) {
bid.cpm = response.price;
bid.crid = response.crid;
bid.vastXml = response.adm;
bid.mediaType = 'video';
}
return bid;
}
To do deals for long-form video (adpod
ad unit) just add the dielTier
integer value to bid.video.dealTier
. For more details on conducting deals in ad pods see our ad pod module documentation.
In order for your bidder to support the native media type:
adUnit.mediaTypes.native
object, as described in Show Native Ads.The adapter code samples below fulfills requirement #2, unpacking the server’s reponse and:
native
object with those assets./* Does the bidder respond with native assets? */
else if (rtbBid.rtb.native) {
/* If yes, let's populate our response with native assets */
const nativeResponse = rtbBid.rtb.native;
bid.native = {
title: nativeResponse.title,
body: nativeResponse.desc,
cta: nativeResponse.ctatext,
sponsoredBy: nativeResponse.sponsored,
image: nativeResponse.main_img && nativeResponse.main_img.url,
icon: nativeResponse.icon && nativeResponse.icon.url,
clickUrl: nativeResponse.link.url,
impressionTrackers: nativeResponse.impression_trackers,
};
}
As of the 0.34.1 release, a bidder may optionally return the height and width of a native image
or icon
asset.
If your bidder does return the image size, you can expose the image dimensions on the bid response object as shown below.
/* Does the bidder respond with native assets? */
else if (rtbBid.rtb.native) {
const nativeResponse = rtbBid.rtb.native;
/* */
bid.native = {
title: nativeResponse.title,
image: {
url: nativeResponse.img.url,
height: nativeResponse.img.height,
width: nativeResponse.img.width,
},
icon: nativeResponse.icon.url,
};
}
The targeting key hb_native_image
(about which more here (ad ops setup) and here (engineering setup)) will be set with the value of image.url
if image
is an object.
If image
is a string, hb_native_image
will be populated with that string (a URL).
Every adapter submission must include unit tests. For details about adapter testing requirements, see the Writing Tests section of CONTRIBUTING.md.
For example tests, see the existing adapter test suites.
import * as utils from 'src/utils';
import {config} from 'src/config';
import {registerBidder} from 'src/adapters/bidderFactory';
const BIDDER_CODE = 'example';
export const spec = {
code: BIDDER_CODE,
aliases: ['ex'], // short code
/**
* Determines whether or not the given bid request is valid.
*
* @param {BidRequest} bid The bid params to validate.
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function(bid) {
return !!(bid.params.placementId || (bid.params.member && bid.params.invCode));
},
/**
* Make a server request from the list of BidRequests.
*
* @param {validBidRequests[]} - an array of bids
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: function(validBidRequests) {
const payload = {
/*
Use `bidderRequest.bids[]` to get bidder-dependent
request info.
If your bidder supports multiple currencies, use
`config.getConfig(currency)` to find which one the ad
server needs.
Pull the requested transaction ID from
`bidderRequest.bids[].transactionId`.
*/
};
const payloadString = JSON.stringify(payload);
return {
method: 'POST',
url: ENDPOINT_URL,
data: payloadString,
};
},
/**
* Unpack the response from the server into a list of bids.
*
* @param {ServerResponse} serverResponse A successful response from the server.
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function(serverResponse, bidRequest) {
// const serverBody = serverResponse.body;
// const headerValue = serverResponse.headers.get('some-response-header');
const bidResponses = [];
const bidResponse = {
requestId: bidRequest.bidId,
cpm: CPM,
width: WIDTH,
height: HEIGHT,
creativeId: CREATIVE_ID,
dealId: DEAL_ID,
currency: CURRENCY,
netRevenue: true,
ttl: TIME_TO_LIVE,
referrer: REFERER,
ad: CREATIVE_BODY
};
bidResponses.push(bidResponse);
};
return bidResponses;
},
/**
* Register the user sync pixels which should be dropped after the auction.
*
* @param {SyncOptions} syncOptions Which user syncs are allowed?
* @param {ServerResponse[]} serverResponses List of server's responses.
* @return {UserSync[]} The user syncs which should be dropped.
*/
getUserSyncs: function(syncOptions, serverResponses) {
const syncs = []
if (syncOptions.iframeEnabled) {
syncs.push({
type: 'iframe',
url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html'
});
}
if (syncOptions.pixelEnabled && serverResponses.length > 0) {
syncs.push({
type: 'image',
url: serverResponses[0].body.userSync.url
});
}
return syncs;
}
/**
* Register bidder specific code, which will execute if bidder timed out after an auction
* @param {data} Containing timeout specific data
*/
onTimeout: function(data) {
// Bidder specifc code
}
/**
* Register bidder specific code, which will execute if a bid from this bidder won the auction
* @param {Bid} The bid that won the auction
*/
onBidWon: function(bid) {
// Bidder specific code
}
/**
* Register bidder specific code, which will execute when the adserver targeting has been set for a bid from this bidder
* @param {Bid} The bid of which the targeting has been set
*/
onSetTargeting: function(bid) {
// Bidder specific code
}
}
registerBidder(spec);
Within a few days, the code pull request will be assigned to a developer for review. Once the inspection passes, the code will be merged and included with the next release. Once released, the documentation pull request will be merged.
The Prebid.org download page will automatically be updated with your adapter once everything’s been merged.