ORTB Bidding Onboarding Guide

Facebook Audience Network has an ORTB bidder which competes for app, mobile web, and in-stream video impressions; either direct from the client, or server-to-server. We support both direct publisher integrations and mediated integrations. This guide describes

Introduction

The principle behind real-time bidding (RTB) is to maximize yield for every impression by replacing the inefficient and complex waterfall with a fair auction. Every demand source has the opportunity to compete and win every impression, when the value is the highest.

Not all demand sources support real-time bids, so most setups include a step where real-time bids are compared with estimated CPMs from other demand sources (e.g. based on historical average CPMs). Sometimes the auction process will make this comparison (i.e. the auction makes the final decision of who gets the impression), and other times the winning bid from the real-time auction will be passed to an ad server to compete against non-RTB demand in a waterfall (i.e. the ad server makes the final decision). The auction can be hosted client-side (e.g. Header Bidding), or server-side.

Header Bidding - Client-Side Auction followed by Ad Server Waterfall

This very common web page setup sees JavaScript (the Wrapper) on the page request bids from multiple RTB demand sources. A timeout is used, and all bids received before the auction times out are compared. The highest bid (and bidder) is passed to the ad server as part of the normal ad request, and it will match a bidding line item at the bid value. The ad server decides who gets the impression - typically direct-sold orders will have a higher priority than the bidding orders, so direct would win over a bid of any value.

server-to-server bidding with ad server - server-side auction followed by ad server waterfall

Header bidding has several disadvantages, all to do with sending the bid requests from the client. Each additional demand source in the auction means additional network calls from the client, so the auction is normally limited to 5-10 demand sources. The network calls are often made over low-bandwidth connections, so the auction needs a long timeout, typically around 1 second. Bidding configuration has to be pushed with every page load. The wrapper code adds page weight (download time and execution time).

To combat these, server-to-server (S2S) bidding is becoming more popular, either in-house or provided by an ad tech partner. This sees the client (web page or app) make a request to an auction server. The auction server requests bids from all bidders, picks the winner, and passes it back to the client. The client then passes the high bid and bidder to the ad server to compete in the waterfall, as in the Header Bidding description above.

Because the auction happens server-side, the client doesn't need a complex wrapper, doesn't need bidding configuration, and doesn't need to make multiple bid requests to all the demand sources. The server can use its better bandwidth to make calls to more demand sources, with shorter timeouts, for a faster and higher-yielding auction that's simpler to configure.

There is an additional complexity for S2S web auctions (not app). User identity needs to be passed from the client to the bidders. However, this identity is normally stored in cookies on the bidders' domains, so is not accessible to the auction server. A separate ID-sync mechanism needs to be set up so the auction server can pass the bidder's ID for the user to each bidder.

Combined Auction - auction includes estimated bids from non-RTB; makes final decision

The models above are designed to work with a commercial ad server that doesn't handle real-time bidding. They first run an RTB auction, then pass the winner to a waterfall. This two-step process adds significant latency. Publishers with an in-house mediation stack can combine the steps into a single combined auction. Again, this can be client-side or server-side.

A server-side implementation would have the client (web page or app) send an ad request to the ad server. The ad server would request bids from each RTB bidder. It would also compute a synthetic bid (predicted/estimated bid) for each non-RTB bidder (e.g. direct-sold, in-house, etc). These bids would all compete in a single auction with the winner returned to the client.

This approach can also run client-side. The client would be passed pre-computed non-RTB synthetic bids as configuration. Then, it would request bids from the RTB bidders and compare these with the non-RTB synthetic bids, and display an ad from the winning demand source directly.

Another server-side model, if the existing stack is heavily waterfall-based, would have the RTB auction high bid (and bidder) passed to the waterfall for the final decision. This all happens in the same ad server process (rather than the auction winner being passed back to the client, then passed to a separate ad server, as in the first two models).

Platform Registration

A platform is a mediation partner who requests bids on behalf of publishers. Platforms have platform IDs which is a FB Application ID that will be used for every request platform is handling on behalf of their publishers.

Publishers who request ORTB bids directly should skip this section and use their app ID as the platform ID.

Please create a new Audience Network app representing your platform. We need the ID to enable bidding endpoints and features.

  • Go to the Facebook Developer Site, create a new Facebook App ID.
  • Let us know the app ID (this is your platform ID).
  • We recommend you follow publisher registration steps to test your platform integration. The business ID that you have created through publisher registration will also need whitelisting as a bidding publisher in order to receive non-test bidding responses.

Publisher Registration

Each publisher needs to create an Audience Network business ID, which we will whitelist for bidding. Under this business id you can create multiple properties that utilizes bidding end point.

  • Go to the Facebook Audience Network documentation, click the Start Now button to create a new Business ID or you can use your existing business ID.
  • You can create properties, apps or domains (mobile web), under the same buisness ID but you will have to register them see Monetization Manager for more details. For web domains, this is only be possible once the app ID has been approved by Facebook.
  • Let us know the business ID (this is your publisher ID).
  • You can manage user access to your business/app.

Passing Identity

Our bidder expects an identity token in the ORTB request in user.buyeruid. This token is generated in different ways for different platforms.

Android

Call com.facebook.ads.BidderTokenProvider.getBidderToken() from the Audience Network SDK, on a background thread. The token is valid for the lifespan of the application process. Also pass the IDFA in device.ifa.

iOS

Call [FBAdSettings bidderToken] from the Audience Network SDK. The token is valid for the lifespan of the application process. . Also pass the IDFA in device.ifa.

Web - Cookie Sync (PIXELS)

You store an FB-issued user-token (e.g. in a cookie, or a server-side match table) for each user/session, and pass it to us with each bid request. If you don't have a token from us, you will fire an FB pixel from the page instead of requesting a bid. We 302-redirect the pixel request to your endpoint, adding our user-token (or 0 for no-match/won't-bid) to the redirect URL. Our cookie-sync endpoint is https://www.facebook.com/audiencenetwork/idsync.

The endpoint URL takes two query parameters:

  • partner is your platform ID
  • callback is the URL-encoded URL we should redirect to

We need to whitelist the domains and paths you'll use and configure the placeholder in the query parameters, which we will replace with the user token. For example, if we redirect to https://platform.com/idsync?partner=FAN&token={TOKEN}, and your platform ID is 123, and your agreed token macro is $BUYERUID, then the pixel URL would be

"https://www.facebook.com/audiencenetwork/idsync?partner=123&callback=https%3A%2F%2Fplatform.com%2Fidsync%3Fpartner%3DFAN%26token%3D%24BUYERUID"

You should refresh the tokens at least once every seven days. We will not payout on wins where the actual user does not match the token user (e.g. the token was issued when a different user was logged in to Facebook on the device).

Web - Iframe

To minimize missed bid-opportunities due to cookie-syncing, we have an alternative token mechanism using a low-latency facebook.com (http://facebook.com/) iframe on the page, which generates a token on every page-load. Your client side script inserts an FB domain iframe to the publisher's page on page load, sets up an event listener to "message" the event to receive the user-token, sends the message to the FB iframe via window.postMessage() API asking for the user-token, and passes it to us with each bid request. If your listener doesn't receive a token within the timeout, you pass an empty user-token to us. The iframe uses local storage so there is no need to additionally cache the token. It is recommended to turn on caching in the browser to reduce iframe loading time.

  1. Insert an iframe on the page from https://www.facebook.com/audiencenetwork/token/v1/.
    try {
          const fbIframe = document.createElement('iframe');
          fbIframe.src = 'https://www.facebook.com/audiencenetwork/token/v1/';
          document.body.appendChild(fbIframe);
        } catch (e) {
          logInsertingFailure(e);
        }
  2. Set up an event listener to receive the user-token.
    function receiveFBToken(event){
      const origin = event.origin;
      1. Verify origin is https://www.facebook.com/
      2. Verify event.source is fbIframe
      3. Read event.data which is the fbToken
    } window.addEventListener("message", receiveFBToken, false);
  3. Send postMessage to the FB iframe asking for the user-token.
    fbIframe.postMessage("GET_FB_TOKEN_{platformid}", 
                         "https://www.facebook.com");
  4. Receive a response in JSON format. 'fbToken' field is the user token required. 'expAfter' field is the unix timestamp of expiration time.
    {"fbToken":"V4_GGIBw1CfOqoYahcJgpwslwUOAqIQr68dgdrLCOv0KsFfXsf79araeoJ4JqRCUxRRidWri26EhDTHcciY5jWWgFFsfDpFYD4p2pHj6AOxdQ0WB7JdAtfs_tablxZCj4IeltODwxhEAQggUtts6X0fdv9ds8zL6WNBUkDzGhqVaqTMeXA5FzgHCJUiFgO81PqAGauZWlcym6IPD-DGRUQtOFgHyq6vYnZyE6sA","expAfter":"1502305165414"}

The Bidding Endpoint

Our server-to-server bidder is found at http://an.facebook.com/placementbid.ortb. You should post an OpenRTB request as Content-Type: application/json. No-bids (punts) are returned as an HTTP 204 error code. We set the following HTTP headers (on both bids and no-bids)

  • X-FB-AN-Request-ID: The Request ID is needed for Audience Network to debug a specific request. Please capture it whenever asking for support.
  • X-FB-AN-Errors: A list of errors encountered, useful to understand reasons for no-bids.
  • X-FB-AN-Bid-Count: The number of bids in the response.

ORTB Request and Response

ORTB Request

Our server-to-server endpoint supports OpenRTB protocol v2.5.

Web Requests

'id' => string // platform's auction identifier,
'imp' => vec< // slots to bid on
  shape(
    'id' => string, // platform's identifier for this slot auction
    'banner' => ?shape(
      'w' => int, // width
      'h' => int, // height
    ),
    'native' => ?shape(
      'w' => int, // width
      'h' => int, // height
    ),
    'video' => ?shape(
      'w' => int, // width
      'h' => int, // height
      'linearity' => int, // 1 = instream
    ),
    'tagid' => string, // Placement ID or Placement Name
  )
>,
'site' => shape(
  'page' => string, // web page URL
  'publisher' => shape(
    'id' => string, // Publisher Business ID
  ),
),
'device' => shape(
  'ua' => string, // device user-agent, required
  // Do not send ip or ipv6 if you are requesting bids from the client device
  // For server-side bid requests only, set one of ip or ipv6
  'ip' => ?string, // device IPv4 (optional, server-side bidding only)
  'ipv6' => ?string, // device IPv6 (optional, server-side bidding only)
  'dnt' => ?int, // 0: normal, 1: do-not-track
),
'user' => shape(
  'buyeruid' => string, // Audience Network Identity Token 
),
'ext' => shape(
  'platformid' => string, // Mediation partner Platform ID (FB App ID)
),
'at' => int, // auction type: 1: First Price, 2: Second Price
'tmax' => int, // auction timeout, ms
'test' => ?int, // 0: normal, 1: test mode (we bid $99.99, but don't pay out)  (optional)
);

Site.Publisher.ID

The site.publisher.id field identifies the Audience Network account. We accept either App ID, or Business ID. If you pass Business ID, we will lookup the App ID by the domain of site.page - i.e. first create an app and property per domain, create placements within that property, and register the domain.

App Requests

'id' => string // platform's request identifier,
'imp' => vec< // slots to bid on
  shape(
    'id' => string, // platform's identifier for this impression within the request
    'banner' => ?shape(
      'w' => int, // width
      'h' => int, // height
    ),
    'native' => ?shape(
      'w' => int, // width
      'h' => int, // height
    ),
    'video' => ?shape(
      'w' => int, // width
      'h' => int, // height
      'linearity' => int, // 2 = outstream (rewarded), 1 = instream
    ),
    'tagid' => string, // Placement ID or Placement Name
    'instl' => ?int, // interstitial: 0 = normal (default); 1 = interstitial
  ),
>,
'app' => shape(
  'publisher' => shape(
    'id' => string, // Publisher Business ID
  ),
),
'device' => shape(
  'ifa' => string, // device ID sanctioned for advertiser use (IDFA on iOS), required
  // Do not send ip or ipv6 if you are requesting bids from the client device
  // For server-side bid requests only, set one of ip or ipv6
  'ip' => ?string, // device IPv4 (optional, server-side bidding only)
  'ipv6' => ?string, // device IPv6 (optional, server-side bidding only)
  'ua' => ?string, // device user-agent
  'dnt' => ?int, // 0: normal, 1: do-not-track
),
'regs' => ?shape( // optional regulations object
  'coppa' => ?int, // US FTC regulations for Children's Online Privacy Protection Act: 1=child-directed, 0=normal (default)
),
'user' => shape(
  'buyeruid' => string, // Audience Network Identity Token 
),
'ext' => shape(
  'platformid' => string, // Mediation partner Platform ID (FB App ID)
),
'at' => int, // auction type: 1: First Price, 2: Second Price
'tmax' => int, // auction timeout, ms
'test' => ?int, // 0: normal, 1: test mode (we bid $99.99, but don't pay out)  (optional)
);

Ad Type

We currently support four types of ads that can be requested through OpenRTB: banner, native (native or native banner) and video (rewarded or instream video), and interstitial. The objects are mutually exclusive, but one of them is required.

ORTB Response

App and instream video bids are valid for 60 minutes. You can create the ad and cache it at any time within the 60 minutes. Web bids are valid for 10 minutes. Any impressions based on a bid more than 60 minutes (app/instream) or 10 minutes (web) old won't be paid.

'id' => string // platform's request identifier
'seatbid' => vec<
  shape(
    'bid' => vec<
      shape(
        'id' => string, // Our identifier for this bid
        'impid' => string, // platform's identifier for this slot auction
        'price' => float, // Our bid price, in USD cents, CPM basis
        'adm' => string, // Our creative - see Rendering The Ad
        'nurl' => string, // URL to get if we win the impression
        'lurl' => string, // URL to get if we lose the impression
      )
    >,
  ),
>,
'bidid' => string, // Our identifier for this response
'cur' => string, // bid currency: USD

Testing your Implementation

You can still test your integration even if your platform or publisher registration has been approved by Facebook yet. The bid response you will receive will be $99.99 but you will not receive payment for this bid request and response.

All you need to do is to set the test flag on the bidding request to 1:

'test':1

Placements

The ORTB field imp.*.tagid holds the Audience Network identifier for the inventory. Inventory is represented in Audience Network as placements.

A placement has a name and an ID, and either can be used as the tagid in the bid request - e.g. you can use the ad server label for inventory. If an unrecognised placement name is given in the bid request, we will use a catchall placement.

Optimisations

  • New placements take several days to calibrate. If an existing publisher migrates to ORTB bidding, speak to your PSM to rename existing placements to match the ORTB tagid
  • The catchall placement used for unrecognised tagids will be poorly calibrated, and have low bids. Create individual placements for all significant inventory

Price, Auctions, Win/Loss Notifications and Payouts

Prices and Payouts

Prices are quoted in USD on a CPM basis (e.g. 4.25 means we'll pay $4.25/1000 impressions if we win). The same unit must be used for the clearing price in lurl. Payouts are made directly to the publisher.

Auction Mechanics

We always bid on a first-price basis, meaning we pay the amount we bid and we bid assuming we'll pay the full amount. This means we are at a disadvantage in a second-price auction, where other bidders can bid assuming they'll pay less than the amount bid.

Win/Loss Notifications

When correctly rendered, our creative notifies us of the win. If the page or app is closed before the creative renders, then we don't record a win and there can be no payout. We also require a more detailed view of the performance of the system, a breakdown of timeouts, outbids, wins, etc. These must be provided to us via ORTB nurl/lurl. Our lurl contains two flags:

  • ${AUCTION_LOSS}: This should be replaced with ORTB loss-code (explained in the table below)
  • ${AUCTION_PRICE}: This should be replaced with the clearing price for the auction in the same unit as our bid (i.e. USD on CPM basis).
EventDescriptionORTB v2.5 Loss Reason

Invalid bid response

Bid is invalid (but on-time, not a no-bid, and valid enough that you can extract the nurl)

3

Bid timeout *

Bid response received, but too late for auction cutoff

2

No bid

No-bids are indicated as HTTP 204 (i.e. no nurl to call), but you may interpret our response as a no-bid (likely an integration issue). You may also request bids for several impressions, and we bid on some but not all.

9

Not highest RTB bidder

Another bidder beat us, including synthetic bids (e.g. non-RTB exchanges), if they are entered into the same auction.

102

Inventory didn't materialise

Our bid won the auction, but the impression didn't materialize (e.g. page wasn't long enough to include this slot, or the user exited the app before the cached ad was used.) Not all partners can provide this (it's a non-event), so we will infer it if not provided.

4902

Sent to ad server

Send this if the last touchpoint you have with the decision process is sending our high bid to the ad server. The impression may still be lost through missing line items, the ad server overruling the auction, or the inventory not materializing.

4900

RTB winner not picked by ad server

We won the RTB auction, but the ad server overruled the auction (e.g. direct).

4903

Win

We won the full decision tree, and tag was placed on page (web) or ad object was cached (app). Viewable impression may still not result.

0

Timeout lurl

For the case of bid timeout, we provide you with an alternative reporting route. It is the generic lurl which might be called upon without the need to wait for the bid to arrive. The format is as follows:

"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&auction=${AUCTION_ID}&ortb_loss_code=2"
ParamTypeDescription

PARTNER_FBID

Int

Ad auction partner id issued by Facebook. Use your app id here if you don't have a dedicated ad auction partner.

APP_FBID

Int

Facebook-issued Id of the application/business which initiated an auction.

AUCTION_ID

String

Client-generated id of the auction you used for issuing a bid request.

Viewability

For app impressions, we only pay out if the impression is viewed, regardless of whether we were the highest bidder. For web impressions, we pay out even if the impression is not viewed, but we include a viewability estimate in our bids so we'll bid lower for low-viewability inventory.

Rendering an Ad

Web

The creative should be written directly into a supported ad server using a friendly iframe or safe frame. It should not be enclosed in an additional iframe, nor placed in a cross-domain iframe.

App

On iOS, the sample code will look similar to the following:

// initialize phase
self.adView =
  [[FBAdView alloc] initWithPlacementID:placementID
                                 adSize:kFBAdSizeHeight50Banner
                     rootViewController:(UIViewController *)[NSObject new]];

// sometime later (after the adm field was saved into a bidPayload variable
[self.adView loadAdWithBidPayload:bidPayload];

On Android, the API is mirrored. Create an ad object with new com.facebook.ads.AdView(...). Call com.facebook.ads.AdView.loadAdFromBid(payload) on the ad object, passing the adm field from the bid.

// Create a new ad
Ad adView = new AdView(activity, "YOUR_PLACEMENT_ID", size);

// Add it to the view hierarchy
adContainer.addView(adView);

// Set a listener to get notified on changes or when the user interacts with the ad
adView.setAdListener(...);

// Load the ad, passing the payload supplied in the ORTB bid response (adm field)
adView.loadAdFromBid(payload);

After AdView.loadAdFromBid(payload) is called, the ad content will be ready to serve for both iOS and Android. You can follow the conventional Audience Network mApp way to display the ad, e.g. render ad view, register ad view, etc.

There is a certain correspondence between OpenRTB request parameters and the type of ad you request on the client. Here are some examples for iOS:

Banner

For the 320x50 “classic” banner display ad:

"imp": [ { "banner": { "h": 50, "w": -1 }... ]

which corresponds to

adView = [[FBAdView alloc] initWithPlacementID:placementID
                                        adSize:kFBAdSizeHeight50Banner];

For the 300x250 “medium rectangle” display ad:

"imp": [ { "banner": { "h": 250, "w": -1 }... ]

which corresponds to

adView = [[FBAdView alloc] initWithPlacementID:placementID
                                        adSize:kFBAdSizeHeight250Rectangle];

Interstitial

"imp": [ { "instl": 1, "banner": { "h": 0, "w": 0 }... ]

which corresponds to

adView = [[FBInterstitialAd alloc] initWithPlacementID:placementID];

Instream Video

"imp": [ { "video": { "h": 0, "w": 0, "linearity": 1 }... ]

which corresponds to

adView = [[FBInstreamAdView alloc] initWithPlacementID:placementID];

Rewarded Video

"imp": [ { "video": { "h": 0, "w": 0, "linearity": 2 }... ]

which corresponds to

adView = [[FBRewardedVideoAd alloc] initWithPlacementID:placementID
                                             withUserID:@"user123"
                                           withCurrency:@"gold" withAmount:1];

Native

"imp": [ { "native": { "h": -1, "w": -1 }... ]

which corresponds to

adView = [[FBNativeAd alloc] initWithPlacementID:placementID];

Native Banner

"imp": [ { "native": { "h": -1, "w": -1 }... ]

which corresponds to

adView = [[FBNativeBannerAd alloc] initWithPlacementID:placementID];

In order to use Native Banner format, you'll need to set the Display Format to be "Native Banner" for your placements on Monetization Manager first.

Latency

  • For minimum latency, request a single bid per API call (i.e., if you are auctioning five slots then call the API five times, each for a single bid).
  • We don't provide region-specific endpoints. The FB infrastructure will route the bid request to the nearest data center.
  • Use HTTP/2 persistent connections.
  • Use the HTTP (not HTTPS) endpoint
  • Populate the ORTB tmax field in the bid request with the auction timeout, and we will choose a bidding strategy balancing latency and performance.
  • To meet latency targets, we may defer some processing to render time (i.e. a faster bid might mean a slower-rendering ad, and a slower bid results in an ad which renders faster).
  • We are actively working on improving latency. We currently recommend a 500ms timeout for server-to-server bidding.