Welcome to the Index Exchange Partner Certification Process!
Below you will find everything you need to complete the certification process and be a part of the Header Tag Wrapper!
README.md
-This is the main documentation file which should contain everything you need to complete the certification process. If anything is unclear, please refer to this document first.integral-ad-science-nob.js
- This is your partner module file, by default it contains a template divided into multiple sections which need to be completed.integral-ad-science-nob-validator.js
- This is the validator file for the configuration object that will be passed into your module.integral-ad-science-nob-exports.js
- A file that contains all of the modules exports (i.e. any functions that need to be exposed to the outside world).spec
- Contains the unit tests for the module.mockPartnerConfig.json
- this is a mock partner config for your module that will be used for unit testing.
- Complete the integral-ad-science-nob.js file
- integral-ad-science-nob.js is where all of your adapter code will live.
- In order to complete the partner module correctly, please refer to the Partner Module Overview and the Utility Libraries sections.
- Please refer to the Partner Requirements and Guidelines when creating your module. Ensure requirements are met to streamline the review process.
- Complete the integral-ad-science-nob-validator.js file
- This file is where your partner-specific configurations will need to be validated.
- Things like type and null checks will be done here.
- Complete the integral-ad-science-nob-exports.js file
- This file will contain any functions that need to be exported or exposed to the outside world. Things like render functions, custom callbacks, etc. Any legacy render functions will also need to be exposed here. Anything added to the
shellInterface.IntegralAdScienceNob
will be accessible throughwindow.headertag.IntegralAdScienceNob
- This file will contain any functions that need to be exported or exposed to the outside world. Things like render functions, custom callbacks, etc. Any legacy render functions will also need to be exposed here. Anything added to the
- Run & Create Unit tests for your module
- Inside the spec folder you will find a set of basic unit tests for your module.
- You must pass these basic unit tests before submitting your module.
- In addition, you should create more specific unit tests with actual values based on mock ad responses to confirm that your module is working as expected.
- Please refer to the Testing section below for more information.
- Submitting for Review
- Once the module has been verified submit a pull request from the
development-v2
branch to themaster-v2
branch for the Index Exchange team to review. If everything is approved, your adapter will be officially certified!
- Once the module has been verified submit a pull request from the
Our standard creative tag in dfp looks as follows:
<script type="text/javascript">
var w = window;
for (var i = 0; i < 10; i++) {
w = w.parent;
if (w.headertag) {
try {
w.headertag.IntegralAdScienceNob.render(document, %%PATTERN:TARGETINGMAP%%, '%%WIDTH%%', '%%HEIGHT%%');
break;
} catch (e) {
continue;
}
}
}
</script>
In order for your module to be successfully certified, please refer to the following list of requirements and guidelines. Items under required must be satisfied in order to pass the certification process. Items under guidelines are recommended for an optimal setup and should be followed if possible.
- The only targeting keys that can be set are predetermined by Index. The partner module should not be setting targeting on other keys.
- Must support the following browsers: IE 9+, Edge, Chrome, Safari, and Firefox
- Please use our helper libraries when possible. Refer to our Utility Libraries documentation below. All of the utility functions in this library are designed to be backwards compatible with supported browsers to ease cross-browser compatibility.
- Must provide cache busting parameter. Cache busting is required to prevent network caches.
- Partner endpoint domain must be consistent. Load balancing should be performed by the endpoint.
- Your endpoint should support HTTPS request. When wrapper loads in secure pages, all requests need to be HTTPS. If you're unable to provide a secure endpoint, we will not be able to make requests to your ad servers.
- Your module should support a single request architecture (SRA) which has a capability to send multiple bid requests in a single HTTP request.
- Partner should use AJAX method to make bid requests. Please use our Network utility library for making network requests, particularly the
Network.ajax
method. This ensures the requests will work with all browsers.
- Returned bid must be a net bid.
- Pass on bid must be explicit in the response.
- All returned bids shall be in the currency trafficked by the publisher.
- Size must be returned in the response. The sizes must also match the requested size. This size parameter will be validated.
- Encrypted prices must be flagged in the response.
- Pixels/beacons must only be dropped only when the partner wins the auction and their ad renders.
- Dropping pixels during auction slows down the execution of auctions and is not allowed by Index Exchange.
- DFP line items, creatives, and render functions will be set up by the Index Exchange team.
The Header Tag Wrapper passes each partner a configuration object that has been configured based on a publisher's website. This object contains all the configuration information required for the wrapper, including partner-specific slot mappings, timeouts, and any other partner-specific configuration. The partner-specific slot mappings dictate how ad slots on the page will map to partner-specific configuration. There are 2 concepts to be familiar with when understanding how slots on the page are mapped back to partner-specific configuration. Header Tag Slots, which refers to htSlots and partner-specific configuration, which refers to xSlots in the codebase.
- htSlots - This is an abstraction of the googletag.slot object.
- These will need to be mapped to xSlots.
- xSlots - These are also an abstraction for partner-specific configuration that will be mapped to htSlots.
- These represent a single partner-specific configuration.
- An xSlot is how a partner can map their ad server specific identifiers (placementIDs, siteIDs, zoneIDs, etc) to the
htSlot
object. - It can represent a single or multiple sizes.
- Multiple xSlots can be mapped to the same htSlot.
Example Partner Configuration Mapping
{
"partners": {
"IntegralAdScienceNob": {
"enabled": true,
"configs": {
"xSlots": {
"xSlot1": {
"placementID": "123",
"sizes": [ [300, 250], [300, 600] ]
},
"xSlot2": {
"placementID": "345",
"sizes": [ [300, 250] ]
}
},
"mapping": {
"htSlotID-1": [ "xSlot1" ],
"htSlotID-2": [ "xSlot2" ]
}
}
}
}
}
Based on the mapping defined in the partner config, Parcels will be generated for every xSlot/htSlot pair. Parcels are objects that carry different kinds of information throughout the wrapper. In the context of the adapter, parcels are the input into your adapter. Parcels carry information regarding which slots on the publisher's page need demand. More specifically parcels contain information like xSlot reference, htSlot references, demand (after its been applied by the adapter in parseResponse), etc. Each parcel represents a single combination of an htSlot and an xSlot.
Each parcel is an object in the following form:
{
"partnerId": "IAS",
"htSlot": {
"__type__": "HeaderTagSlot"
},
"ref": "googletag.Slot", // reference to the slot on the page, in this example it is a googletag slot
"xSlotRef": { // contains the reference to the xSlot object from the partner configuration.
"placementID": "123",
"sizes": [ [300, 250], [300, 600] ]
},
"xSlotName": "1",
"requestId": "_fwyvecpA"
}
These parcels will be fed into both of the functions that your adapter needs to implement:
-
First
generateRequestObj
will need to craft a bid request for theparcels
that need demand. -
Second
parseResponse
will take the same parcels and apply the demand (set specific properties in the parcel objects) that is returned from the bid requests to the same parcels and send them back to the wrapper.
- The Header Tag Wrapper script tag is loaded on the page.
- Wrapper specific configuration validation is performed.
- All the partner modules are instantiated.
- Partner-specific configuration validation is performed - checking that all the required fields are provided and that they are in the correct format.
- An external request for demand is made to the wrapper. This can be via a googletag display or refresh call, or by other methods depending on the wrapper product in use.
The wrapper requests demand from the partner modules for the required slots (provided in the form of parcels).
- The wrapper calls
generateRequestObj(returnParcels)
for every partner module. - The adapter then crafts and returns a request object based on the parcels (containing slot information) specified.
- The wrapper then sends out a bid request using the request object.
- Depending on how the adapter is set up and whether jsonp is supported, a response callback (
adResponseCallback
) is called. - The adapter parses the response (
parseResponse
) and attaches the demand to the same returnParcels. It also registers the ad creative with the wrapper's render service. - The returnParcels are then sent back to the wrapper.
- The wrapper calls
- The wrapper applies targeting using the demand from the returnParcels.
- If the partner wins the auction in the ad server, their creative code will be returned and executed.
- The creative code contains a call to the wrapper's render function.
- The partner ad is rendered.
In this section you will be filling out the integral-ad-science-nob.js, integral-ad-science-nob-exports.js, and the integral-ad-science-nob-validator.js files to create your module.
Before you get started on writing the actual code for your module, you need to figure out what your partner configuration (refer to Configuration) object will look like. This is crucial because it will determine the input (parcels) to your module's core functions.
Once you have a basic idea of what this will look like, and how you will uniquely identify each slot on your server (via xSlot placementId or other inventory codes) you will need to validate this configuration. This validation will be performed by the wrapper using the integral-ad-science-nob-validator.js
file.
The integral-ad-science-nob-validator.js
file contains a single export, a partnerValidator
function, that takes in the configuration object that will be fed to your module's constructor (refer to Configuration for an example layout) and validates it via type checks. The type checks are performed using an external library called schema-inspector
, for which the documentation can be found here https://github.com/Atinux/schema-inspector.
We have provided a very basic validation schema based off of the example mockPartnerConfig.js
object found in the spec/support
directory for testing (refer to the Testing section for the testing structure).
Once you have filled this file out, you can continue to actually writing your module!
This section involves setting up the general partner configuration such as name, default pricing strategy as well as the general format of incoming/outgoing bids for the adapter. Please fill out all of the keys inside the __profile
variable.
- partnerId - This is simply the name of our module, generally if your module is a bidder the name will end with Nob.
- namespace - Should be the same as partnerId, it is the namespace that is used internally to store all of variables/functions related to your module, i.e. adResponseCallbacks.
- statsId - A unique identifier used for analytics.
- version - If this is the first iteration of your module, please leave this field at 2.0.0.
- targetingType - The targeting type of your bidder, the default is slot for slot level targeting but could also be page.
- enabledAnalytics - The analytics that the wrapper will track for the module. requestTime is the only currently supported analytic, which records different times around when bid requests occur.
- features - Extra features that a partner can support
- demandExpiry - Setting an expiry time on the demand that partner returns.
- rateLimiting - Used for limiting the amount of requests to a given partner for a given slot on pages that support rate limiting in dfp.
- targetingKeys - Different targeting keys that will be used to record demand for a given parcel.
- id - This key will be used to trace back the creative that has won in dfp for rendering.
- om - This key signals the open market bid in cpm.
- pm - This key signals the private market bid in cpm.
- pmid - This key signals the private market deal id.
The last three properties are critical for the wrapper to understand how to interact with the endpoint:
- callbackType:
- Partner.CallbackTypes.ID -
Use this option if your endpoint accepts an arbitrary identifier in requests which will be returned in the matching response. You will need to set this callbackId in the
generateRequestObj
function and retrieve it in the adResponseCallback function. This is the preferred method for matching requests and responses. - Partner.CallbackTypes.CALLBACK_NAME - Use this option if your endpoint has no parameter which can be used as a callback ID. The wrapper will generate a new callback function for each request, and use the function name to tie requests to responses.
- Partner.CallbackTypes.NONE - Use this option if your endpoint supports AJAX only and will return a pure JSON response rather than JSONP with a callback function. In this mode your endpoint will not receive any demand requests if the user is using a browser which does not fully support AJAX, such as Internet Explorer 9 or earlier.
- Partner.CallbackTypes.ID -
Use this option if your endpoint accepts an arbitrary identifier in requests which will be returned in the matching response. You will need to set this callbackId in the
- architecture:
- Partner.Architectures.MRA - Use this option (Multi-Request Architecture) if your endpoint requires a separate network request for each ad slot. In this mode your endpoint will receive one network request for every xSlot mapping active in the current wrapper demand request.
- Partner.Architectures.FSRA - Use this option (Fully Single-Request Architecture) if your endpoint can handle any number of slots in a single request in any combination, including multiple requests for the same slot. In this mode your endpoint will receive a single network request per wrapper demand request.
- Partner.Architectures.SRA - Use this option (Single-Request Architecture) if your endpoint can handle any number of slots in a single request, but cannot repeat the same xSlot more than once in a given request. e.g., you use a placementId and can receive a request for multiple placementIds at once, but not for the same placementId twice. The wrapper will arrange the mapped xSlots into the minimum possible number of network requests to your endpoint.
- requestType:
- Partner.RequestTypes.ANY - Use any request type when making requests. The wrapper will attempt to make an AJAX request for your bid requests, if XHR is not supported, the wrapper will attempt to make a JSONP request if possible.
- Partner.RequestTypes.AJAX - Use only AJAX for bid requests. Note, if the browser does not support ajax the bid requests will not go out.
- Partner.RequestTypes.JSONP - Use only JSONP for bid requests.
Please also fill out the bidTransformerConfigs
according to your module's bid response cpm format.
Refer to the below BidRoundingTransformer section for details on how the bidTransformer works. It is crucial that you fill this out correctly and output your bid in CENTS, otherwise all of your cpm values will be incorrectly formated (i.e. passing dollars instead of cents).
This step is for crafting a bid request url given a specific set of parcels.
For this step, you must fill out the generateRequestObj(returnParcels)
function. This function takes in an array of returnParcels.
These are the parcel objects that contain the different slots for which demand needs to be requested.
The wrapper will ensure that the array contains an appropriate set of parcels to pass into a single network request for your endpoint based on the value set in __profile.architecture. Note, in the particular case your architecture is MRA, this array will have length 1.
Using this array of parcels, the adapter must craft a request object that will be used to send out the bid request for these slots. This object must contain the request URL, an object containing query parameters, and a callbackId.
The final returned object should looks something like this:
{
url: 'http://bidserver.com/api/bids' // base request url for a GET/POST request
data: { // query string object that will be attached to the base url
slots: [
{
placementId: 54321,
sizes: [[300, 250]]
},{
placementId: 12345,
sizes: [[300, 600]]
},{
placementId: 654321,
sizes: [[728, 90]]
}
],
site: 'http://google.com'
},
callbackId: '_23sd2ij4i1' //unique id used for pairing requests and responses
}
More information can be found in the comment section of the function itself.
Once the request from Step 2 finishes the adResponseCallback
will be called to store the returned response in a adResponseStore
object.
If __profile.callbackType
is set to CALLBACK_NAME
or NONE
, the wrapper will handle the callback for you and you can remove this function. If it is set to ID, you must retrieve the callback ID from the network response and store that response in the _adResponseStore
object keyed by the callback ID.
See the function in the template for details.
In this step the adapter must parse the returned demand from the bid response and attach it the returnParcels objects.
The returnParcels array will be one of the same arrays that was passed to generateRequestObj
earlier.
This step involves first matching the returned bids to the internal returnParcels objects. This can be done via some identifier that was setup for an xSlot (for example, a placementId) in the partner configuration and the same id being present in the bid response object.
This function first iterates over all of the returned bids, parsing them and attaching the demand to the returnParcel objects (which will be implicitly passed back to the wrapper). This step also involves registering the creative (if returned) with the render service, which is responsible for storing and rendering that creative if the corresponding demand wins the DFP auction.
In order to complete this step correctly, please fill out the section which includes the matching criteria. This step is necessary to map returnParcel objects to the returned bids correctly. In order to do this, we need to use some common criteria that is present both in the xSlot configuration and in the returned bids (usually placementIds or inventory codes).
Also please fill out each of the variables for each bid, these will be attached to the parcel objects to store the demand:
- bidPrice - The bid price for the given slot
- bidWidth - The width of the given slot
- bidHeight - The height of the given slot
- bidCreative - The creative/adm for the given slot that will be rendered if is the winner.
- bidDealId - The dealId if applicable for this slot.
- bidIsPass - True/false value for if the module returned a pass for this slot.
This step is for rendering the winning creative. If the partner module's line item wins, the creative code will be returned and inserted into the iframe for that googletag slot. The standard creative code will contain a call to the partner module's specific render
function.
The adm passed into this function will be the same bidCreative that was earlier attached to a given parcel object. They are matched via the id targeting key.
The included render
function should work as is for most partners, as it simply involves a call to document.write to write the creative to the iframe in which it was passed into by dfp.
In this step, you will be required to fill out the exports file for your module. This file will contain all of the functions that will need to be exposed to outside page if they need to be accessed outside of the wrapper. In the usual case, all you will need to change in this file is your partner module's name in the included snippet:
shellInterface.IntegralAdScienceNob = { //shell interface is the window variable that is accessable through the window object, currently this will always be window.headertag
render: SpaceCamp.services.RenderService.renderDfpAd.bind(null, 'IntegralAdScienceNob')
};
This snippet, exposes your module's render function to the outside world via the window.headertag
namespace.
If your module requires using a custom adResponse callback via Partner.CallbackTypes.ID callback type, that callback will need to be exposed here. Which would look something like this:
if (__directInterface.Layers.PartnersLayer.Partners.IntegralAdScienceNob) {
shellInterface.IntegralAdScienceNob = shellInterface.IntegralAdScienceNob || {};
shellInterface.IntegralAdScienceNob.adResponseCallback = __directInterface.Layers.PartnersLayer.Partners.IntegralAdScienceNob.adResponseCallback;
}
There are a lot of helper objects available to you in you partner module.
isObject(entity)
- Return true if entity is an object.isArray(obj)
- Return true if obj is an array.isNumber(entity)
- Return true if entity is a number.isString(entity)
- Return true if entity is a string.isBoolean(entity)
- Return true if entity is a boolean.isFunction(entity)
- Return true if entity is a function.isRegex(entity)
- Return true if entity is regex.isEmpty(entity)
- if entity is a string, return true if string is empty.
- if entity is an object, return true if the object has no properties.
- if entity is an array, return true if the array is empty.
arrayDelete(arr, value)
- Delete either given value from an object or array.randomSplice(arr)
- Returns a randomly spliced item from an array.deepCopy(entity)
- Return a deep copy of the entity.mergeObjects(entity1, entity2, ...)
- Takes the first entity and overwrites it with the next entity, returning the final object.mergeArrays(arr1, arr2, ...)
- Merge all of the specified arrays into one and return it.tryCatchWrapper(fn, args, errorMessage, context)
- Wrap the given arguments into a try catch block. Returning a function.isArraySubset(arr1, arr2, matcher)
- Return true ifarr1
is a subset ofarr2
.
now()
- Return the number of milliseconds since 1970/01/01.generateUniqueId(len, charSet)
- Creates a unique identifier of the given length using the optionally specified charset:ALPHANUM
: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',ALPHA
: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',ALPHA_UPPER
: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',ALPHA_LOWER
: 'abcdefghijklmnopqrstuvwxyz',NUM
: '0123456789'
getTimezoneOffset()
- Returns the timezone offset.documentWrite(doc, data)
- doc.write the data using the specified documentdoc
.
arrayToString(arr)
- Returns the string representation of an array in the form of '300x250'.stringToArray(str)
- Return the array rep representation of a string in the form of [300, 250].
getProtocol(httpValue, httpsValue)
- Returndocument.location.protocol
orhttpValue
ifdocument.location.protocol
is http andhttpsValue
ifdocument.location.protocol
is https.isLocalStorageSupported()
- Checks if local storage is supported.getViewportWidth()
- Return viewport width.getViewportHeight()
- Return viewport height.isTopFrame()
- Checks to see if the code is being run in the top frame or iframe.getScreenWidth()
- Returns screen.width.getScreenHeight()
- Returns screen.height.getReferrer()
- Return document.referrer.getPageUrl()
- Return the page's url.getHostname()
- Return the page's hostname.getNearestEntity(entityName)
- Returns the entity withentityName
in the nearestwindow
scope.createHiddenIFrame(srcUrl, scope)
- Generate a hidden iframe and then append it to the body. Use thesrcUrl
andscope
if provided.
getDeviceType()
- Returns the device type.
transformBid(rawBid)
- Transform rawBid into the configured format. This includes, rounding/flooring according to the bidTransformConfig that was used to instantiate the library. The bidTransformConfig is an object of the format:floor
- Minimum acceptable bid price.inputCentsMultiplier
- Multiply input bids by this to get cents.outputCentsDivisor
- Divide output bids in cents by this.outputPrecision
- Decimal places in output.roundingType
- Should always be 1.buckets
- Buckets specifying rounding steps.
Example of bidTransformConfig
:
var bidTransformConfig = { // Default rounding configuration
'floor': 0,
'inputCentsMultiplier': 100, // Input is in dollars
'outputCentsDivisor': 100, // Output as dollars
'outputPrecision': 2, // With 2 decimal places
'roundingType': 1, // Floor instead of round
'buckets': [{
'max': 2000, // Up to 20 dollar (above 5 cents)
'step': 5 // use 5 cent increments
}, {
'max': 5000, // Up to 50 dollars (above 20 dollars)
'step': 100 // use 1 dollar increments
}]
};
For our unit testing suite we are using a combination of jasmine as a test runner, chai for assertion and schema-inspector for type validation.
The spec folder is where all of the testing files are contained and is structured as follows:
- spec - The main folder for all of unit testing
- support - Contains various stubs and mock data
mockPartnerConfig.json
- Contains your module mock partner config that is used throughout the suitelibraryStubData.js
- Stubs for various librariespartnerStub.js
- Stub for the partner libraryjasmine.json
- Jasmine test runner config
generateRequestObj.spec.js
- The spec for testinggenerateRequestObj
functionparseResponse.spec.js
- The spec for testingparseResponse
functionprofile.spec.js
- The spec for testing the partner'sprofile
configuration
- support - Contains various stubs and mock data
As you can see, we have divided the spec files based on the different functions/areas that you need to fill out for your module.
In order to submit your module for review, you must first pass all of the basic tests cases included in the repository. In order to run these tests you must first install all the necessary npm packages by running npm install
and then simply run the command npm test
from the root of the repository to execute the tests. NPM version 3.8.6
and node version v6.1.0
were used when developing these tests.
Before running the npm test
command you must first fill out both the mockPartnerConfig.json
to contain a correct sample configuration for your partner. Based on this configuration, parcels will be generated. For SRA partners, all of the slots outlined in your mockPartnerConfig will have corresponding parcels generated. For MRA, only the first xSlot/htSlot combination will have a parcel generated and fed into your module's functions.
You will also need to fill out the various adResponseMock
variables through out the specs to match the mockPartnerConfig.json
. These should contain the correct adResponses to satisfy each of the parcels that were generated fro the mockPartnerConfig.json
This data will be fed into your partner modules and the inputs will be tested.
For example, mockPartnerConfig.json
contains a sample partner config. Here are the different returnParcels
arrays that will be generated based on the module's architecture.
var returnParcels_SRA = [{
partnerId: 'IntegralAdScienceNob',
htSlot: { getId: function () {
return "htSlot1"
} },
ref: '',
xSlotRef: { placementId: '54321', sizes: [ [300,250] ] },
requestId: '_1496788873668',
},{
partnerId: 'IntegralAdScienceNob',
htSlot: { getId: function () {
return "htSlot1"
} },
ref: '',
xSlotRef: { placementId: '12345', sizes: [ [300,600] ] },
requestId: '_1496788873668',
},{
partnerId: 'IntegralAdScienceNob',
htSlot: { getId: function () {
return "htSlot2"
} },
ref: '',
xSlotRef: { placementId: '654321', sizes: [ [728,90] ] },
requestId: '_1496788873668',
}]
var returnParcels_MRA = [{
partnerId: 'IntegralAdScienceNob',
htSlot: { getId: function () {
return "htSlot1"
} },
ref: '',
xSlotRef: { placementId: '54321', sizes: [ [300,250] ] },
requestId: '_1496788873668',
}]
These tests perform basic type checks and make sure that all of the required fields have been filled out correctly. For example, if the adResponseMock
returned a bid, each of the parcels above for an SRA architecture bidder should contain new additional fields for price, size, creative, and targeting.
However these tests are very basic and we encourage you to write your own test cases to further confirm the functionality of your module. Please refer to the next section on how to write additional test cases.
In order to create additional test cases and further verify your module, please fill out the ADD MORE TEST CASES TO TEST AGAINST REAL VALUES
section in both generateRequestObj.spec.js
and parseResponse.spec.js
for your corresponding module architecture type.