Integration guide

Follow this guide to integrate Bidboost with your Prebid.js stack, onboard sites and roll out experiments safely. It is recommended you implement everything on a staging/test page first to avoid unwanted surprises when going live.

Create sites and placements

Create site and placement entities using the management UI in the dashboard or the management API. These entities keep identifiers bounded and prevent cardinality explosions caused by misconfigured Prebid.js setups or malicious actors. This is an intentional security measure and helps keep the system predictable.

For sites, choose a name that you have available in your Prebid.js wrapper script (for example, your internal site identifier) so you can configure Bidboost without complicated mappings.

For placements, use a code that is equal to the Prebid.js ad unit code. If that is not feasible, you can provide a mapping function later in the integration flow when integrating the browser script.

Create experiments

Create one experiment per site to split traffic between the control group and the experiment group. During the trial period, we will configure this on your behalf because we can safely choose and adjust the split based on the data we observe.

Start with a high control percentage and a low experiment percentage, then ramp up gradually to reduce revenue risk. If you want an extra safety layer when integrating Bidboost with a site for the first time, apply an upstream split by only loading the Bidboost script for a portion of traffic instead of enabling it on every request. However, keep in mind that only the traffic that loads the Bidboost script will actually record data.

Even after onboarding is complete, it is advised to keep an experiment running with a low control percentage so unoptimized and optimized performance can be continuously monitored.

Integrate the browser script

We will provide the script URL. Load it as early as possible to minimize auction stalling. The script calls our backend to predict auction parameters, and the earlier it sends the request the sooner it receives a response.

<script src="/link/to/bidboost.js" async></script>

Initialize the global Bidboost object and command queue and push the Bidboost-specific setup code to the queue to avoid race conditions. This ensures your setup code runs after the script loads or immediately if the script is already loaded at that point.

window.bidboost ??= {};
window.bidboost.cmd ??= [];
window.bidboost.cmd.push(() => {
  // Setup code handled in the next section
});

Create auction pipeline

Create an auction pipeline scoped to the specific Prebid.js global you want Bidboost to operate on. This allows creating separate auction pipelines in the future, such as for external partners like Aniview (video ads provider) that use Prebid.js internally, so their demand can be optimized too. The auction pipeline should be reused for all auctions, you don't have to recreate it every time.

const auctionPipeline = window.bidboost.createAuctionPipeline({
  // Name of the site created through the management UI or API
  site: "your site",
  // See: Provide ad unit definitions
  adUnits: yourAdUnits.concat(yourAdditionalBidders),
  // See: Provide user ID modules
  userIdModules: yourUserIdConfig,
  // See: Provide user ID access consent
  userIdAccessAllowed: userConsentedToIdAccess,
  // (optional) See: Load bid adapters on the fly
  loadBidAdapter: yourBidAdapterLoader,
  // (optional) Refresh interval of your auctions, re-uses predictions if missing
  refreshIntervalMilliseconds: 30_000,
  // (optional) Name of the Prebid.js global object name, defaults to pbjs if missing
  pbjsGlobalName: "pbjs",
});

Additional performance and behavior tuning parameters are available in the browser script API reference.

Provide ad unit definitions

Keep your existing Prebid.js ad unit setup intact so the control group works correctly. Specify the additional bidders you want to include for bid-stream shaping together with your existing ad unit definitions via the adUnits parameter in createAuctionPipeline().

// Ad unit definitions, your Prebid.js wrapper will have this somewhere
const adUnits = [
  {
    code: "top_leaderboard",
    bids: [
      { bidder: "appnexus", params: {...} },
      { bidder: "openx", params: {...} },
    ],
  },
];
// Keep old Prebid.js setup as-is
window.pbjs.que.push(() => {
  window.pbjs.addAdUnits(adUnits);
});

// Define additional bidders, same shape as Prebid.js ad units
const additionalBidders = [
  {
    code: "top_leaderboard",
    bids: [
      { bidder: "ix", params: {...} },
      { bidder: "sovrn", params: {...} },
    ],
  }
];
// Assume Bidboost is already loaded for simplicity
const auctionPipeline = window.bidboost.createAuctionPipeline({
  // Bidboost will merge and normalize everything for you, this suffices
  adUnits: adUnits.concat(additionalBidders),
  ...
});

The reason we do it this way is to minimize auction stalling. By providing all ad unit and bidder information upfront, we can immediately forward that information to our backend rather than wait for Prebid.js to be loaded.

Provide user ID modules

Provide user ID modules upfront so Bidboost can predict auction parameters without waiting for Prebid.js to finish loading. This again minimizes auction stalling and thus the impact of Bidboost on your TTFA.

// User sync config, your Prebid.js wrapper will have this somewhere
const userSyncConfig = {
  userIds: [{
    name: "sharedId",
    storage: {
      type: "cookie",
      name: "_sharedid",
      expires: 365,
    },
  }],
  ...
};
// Keep old Prebid.js setup as-is
window.pbjs.que.push(() => {
  window.pbjs.setConfig({ userSync: userSyncConfig });
});

// Assume Bidboost is already loaded for simplicity
const auctionPipeline = window.bidboost.createAuctionPipeline({
  userIdModules: userSyncConfig.userIds,
  ...
});

Specify whether the user gave consent for having their user IDs accessed. Bidboost does not read, write, process, or transmit the user IDs themselves; Bidboost only checks for their existence. Failure to set this flag results in less accurate predictions, so integrating with a CMP and setting this flag is strongly recommended.

let userIdAccessAllowed = false;

// Example for TCFv2 API
if (typeof __tcfapi === "function") {
  __tcfapi("addEventListener", 2, (tcData, success) => {
    if (!success || ) {
      return;
    }

    if (success && (
        tcData.eventStatus === "tcloaded" ||
        tcData.eventStatus === "useractioncomplete")
    ) {
      const consents = tcData.purpose?.consents ?? [];
      userIdAccessAllowed = consents[1] === true && consents[7] === true;
      ...
    }
  });
}

// ... wait for consent resolved

const auctionPipeline = window.bidboost.createAuctionPipeline({
  userIdAccessAllowed: userIdAccessAllowed,
  ...
});

(optional) Load bid adapters on the fly

Bid-stream shaping works better the more bidders you get approved for a site and register through the configuration. This, however, increases JavaScript bundle sizes and can impact loading times of the website, even if Bidboost does not end up using the included bidders.

While harder to set up, loading bidders on the fly as needed can significantly speed up website loading times. Bidboost provides the loadBidAdapter configuration setting, which is an asynchronous function that it calls for all bidders included in the next auction. It does not call this function for the predefined bidders in pbjs.adUnits nor bidders whose adapters it already loaded for a previous auction.

Bidboost provides a default implementation for this function which loads the required bid adapters from the jsdelivr CDN. It may suffice for your purposes, but keep in mind that relying on an external CDN is always a risk.

// Assume Bidboost is already loaded for simplicity
const auctionPipeline = window.bidboost.createAuctionPipeline({
  loadBidAdapter: window.bidboost.loadBidAdapterFactory({ pbjsVersion: "v10.20.0" }),
  ...
});

Or, if you already have your own CDN:

// Assume Bidboost is already loaded for simplicity
const auctionPipeline = window.bidboost.createAuctionPipeline({
  loadBidAdapter: window.bidboost.loadBidAdapterFactory({ chunkBaseUrl: "https://cdn.yourcompany.com/prebid.js/dist/chunks" }),
  ...
});

If you omit this setting, Bidboost does not load bid adapters on the fly and instead relies on the bid adapters being present in the initial bundle.

(optional) Provide a placement mapper

This is optional and only applies if either the placements you created via the UI or API do not match the ad unit codes on the page or you dynamically create ad units. The placement mapping function receives an ad unit definition object (the same object shape provided for items in the adUnits array passed to createAuctionPipeline()) and must return a string that matches the placement code in the Bidboost system.

const adUnits = [
  {
    "code": "ad-div-1",
    "internalId": "some-placement",
    "bids": [...],
  }
];
// Assume Bidboost is already loaded for simplicity
const auctionPipeline = window.bidboost.createAuctionPipeline({
  adUnits: adUnits, // Bidboost will shallowly copy each ad unit
  placementMapper: adUnit => adUnit.internalId, // and pass it here
  ...
});

(optional) Provide a bidder mapper

This is optional and only applies if you use bidder aliases instead of the primary Prebid.js bidder codes defined in the bid adapters.

const bidderMapping = {
  cnvr: "conversant",
  smart: "smartadserver",
};
const reverseBidderMapping = {
  conversant: "cnvr",
  smartadserver: "smart",
};
// Assume Bidboost is already loaded for simplicity
const auctionPipeline = window.bidboost.createAuctionPipeline({
  bidderMapper: bidder => bidderMapping[bidder] ?? bidder,
  reverseBidderMapper: bidder => reverseBidderMapping[bidder] ?? bidder,
  ...
});

Wrap auctions in auction pipeline

Simply replace your existing pbjs.requestBids(requestObj) call with auctionPipeline.run(requestObj) so Bidboost can apply bid-stream shaping and ingest the auction results into its analytics system. It has the exact same contract as pbjs.requestBids().

auctionPipeline.run({
  adUnitCodes: ["top_leaderboard", "sidebar"],
  bidsBackHandler: () => {
    const winningBid = pbjs.getHighestCpmBids();
    for (const bid of winningBid) {
      window.pbjs.renderAd(...);
    }
  },
});

Should any error happen during the application of Bidboost optimizations, your auction will be run as-is in an unoptimized manner, as if you would have called pbjs.requestBids() yourself.

Verify performance

Log into the dashboard to review Bidboost performance at the site level (more granular, with each test group shown) or at the network level (all test groups consolidated into a single optimization group). If results look healthy for a day or two, increase the traffic split, wait another day or two to verify again, and repeat until you reach the desired traffic split.

We normalize the revenue and filled impressions by the actual amount of page views each test group received. This makes the metrics comparable across all test groups no matter how many test groups or how big the traffic split. It also completely independent of seasonal fluctuations in revenue and filled impressions.

Test group bleeding

There is an effect that causes the benefits provided by the optimized test groups to bleed into other test groups. This happens because the optimized test groups improve the value of your inventory and the bidders can't differentiate between the optimized and unoptimized inventory - to them it is the same inventory. As a result, the control group will also receive a bit of a boost over time.

There is a way to work around this issue, for example by using different GPIDs (Global Placement Identifiers) and/or using different ad unit codes for the various test groups . Unfortunately, not only does it not work for all bidders, it is also quite risky to change. Ultimately it is not worth the tiny inaccuracy in the data. Just know that if your control group has a small upward trend over time, it does not mean that the optimization works worse than before.

External demand sources

We do not record data for external demand sources like Google, Amazon (if used through apstag) or Aniview, as each of these external demand sources have their own way of querying data and also often have delays or integration issues. This is beyond the scope of our product.

While having an uplift in revenue generated by Prebid.js is a good indicator you will have an overall uplift across demand sources, as our product increases competition between them, you should still compare how much less revenue each non-Prebid.js demand source made and contrast it with your uplifts in Prebid.js.