Audience Network Display Bidding with Prebid.js

This guide outlines how to use Prebid.js's open source wrapper with the Audience Network API to access Audience Network Demand for mobile web display.

Integration Steps

Step 1: Setup Prebid.js

Step 2: Setup DFP

Step 3: Raw Request / Response details

Step 4: Testing and Troubleshooting


Prerequisites

  • A signed addendum
  • A Facebook Audience Network App ID that is approved for Header Bidding
  • Your domain added and approved for Audience Network
  • Existing Prebid.js wrapper integration with other demand partners already integrated

For Audience Network account setup details, you can review the Getting started guide.

1. Setup Prebid.js

var adUnits = [{
  code: 'div-gpt-ad-1486479040361-0',
  sizes: [[300, 250]],
  bids: [{
    bidder: 'audienceNetwork',
    params: {
     placementId: '<YOUR_PLACEMENT_ID>',
     }
   }]
 },];
  • Set up bidder settings (fb_bidid if using Audience Network tag in DFP, fb_adid if using the Prebid Snippet in DFP):
pbjs.bidderSettings = {
          audienceNetwork: {
            adserverTargeting: [
                  {
                      key: "fb_bidid",
                      val: function (bidResponse) {
                          // make the bidId available for targeting if required
                          return bidResponse.fb_bidid;
                      } 
                  },
                  {   
                      key: "hb_bidder",
                      val: function (bidResponse) {
                          return bidResponse.bidderCode;
                      }
                  },
                  {
                    key: "hb_pb",
                    val: function(bidResponse) {
                      return bidResponse.pbMg;
                    }
                  },
              ],
          },
      };

2. Setup DFP

Fetch and Display the Ads

For each ad unit won by Audience Network, manipulate the DOM with Javascript to inject an iframe at the relevant tag. The iframe’s document needs customizing for each placement, passing in the placement ID, the bid ID, and the size for banner ads. The iframe must be friendly and non-sandboxed. If an iframe is not required, use only the first <div> tag from each example (stripping off the <html><head>...</head><body> and </body></html> tags). The ad should be requested and displayed within 10 minutes of the bid being issued. For ad units served directly (not through DFP), the iframe’s document should be:
<html>
  <body>
    <div style="display:none; position: relative;">    
      <script type="text/javascript">    
        var data = {    
          placementid: '%%PATTERN:fb_placementid%%',    
          format: '%%PATTERN:fb_format%%', bidid: '%%PATTERN:fb_bidid%%',    
          testmode: false,    
          onAdLoaded: function(element) {    
            console.log('Audience Network [%%PATTERN:fb_placementid%%] ad loaded');    
            element.style.display = 'block';    
          },    
          onAdError: function(errorCode, errorMessage) {    
            console.log('Audience Network [%%PATTERN:fb_placementid%%] error (' + errorCode + ') ' + errorMessage);    
          }    
        };    
      </script>    
      <script>    
        (function(a,b,c){var d="https://www.facebook.com",e="https://connect.facebook.net/en_US/fbadnw55.js",f={iframeLoaded:true,xhrLoaded:true},g=a.data,h=function(){if(Date.now)return Date.now();else return+new Date()},i=function(B){var C=d+"/audience_network/client_event",D={cb:h(),event_name:"ADNW_ADERROR",ad_pivot_type:"audience_network_mobile_web",sdk_version:"5.5.web",app_id:g.placementid.split("_")[0],publisher_id:g.placementid.split("_")[1],error_message:B},E=[];for(var F in D)E.push(encodeURIComponent(F)+"="+encodeURIComponent(D[F]));var G=C+"?"+E.join("&"),H=new XMLHttpRequest();H.open("GET",G,true);H.send();if(g.onAdError)g.onAdError("1000","Internal error.")},j=function(){if(b.currentScript)return b.currentScript;else{var B=b.getElementsByTagName("script");return B[B.length-1]}},k=function(B){try{return B.document.referrer}catch(C){}return""},l=function(){var B=a,C=[B];try{while(B!==B.parent&&B.parent.document)C.push(B=B.parent)}catch(D){}return C.reverse()},m=function(){var B=l();for(var C=0;C<B.length;C++){var D=B[C],E=D.ADNW||{};D.ADNW=E;if(!D.ADNW)continue;return E.v55=E.v55||{ads:[],window:D}}throw new Error("no_writable_global")},n=function(B){var C=B.indexOf("/",B.indexOf("://")+3);if(C===-1)return B;return B.substring(0,C)},o=function(B){return B.location.href||k(B)},p=function(B){if(B.sdkLoaded)return;var C=B.window.document,D=C.createElement("iframe");D.name="fbadnw";D.style.display="none";D.onload=function(){var E=D.contentDocument.createElement("script");E.src=e;E.async=true;D.contentDocument.body.appendChild(E)};C.body.appendChild(D);B.sdkLoaded=true},q=function(B){var C=/^https?:\/\/www\.google(\.com?)?.\w{2,3}$/;return!!B.match(C)},r=function(B){return!!B.match(/cdn\.ampproject\.org$/)},s=function(){var B=c.ancestorOrigins||[],C=B[B.length-1]||c.origin,D=B[B.length-2]||c.origin;if(q(C)&&r(D))return n(D);else return n(C)},t=function(B){try{return JSON.parse(B)}catch(A){i(A.message);throw A}},u=function(B,C,D){if(!B.iframe){var E=D.createElement("iframe");E.src=d+"/audiencenetwork/iframe/";E.style.display="none";D.body.appendChild(E);B.iframe=E;B.iframeAppendedTime=h();B.iframeData={}}C.iframe=B.iframe;C.iframeData=B.iframeData;C.tagJsIframeAppendedTime=B.iframeAppendedTime||0},v=function(B){var C=d+"/audiencenetwork/xhr/?sdk=5.5.web";for(var D in B)if(typeof B[D]!=="function")C+="&"+D+"="+encodeURIComponent(B[D]);var E=new XMLHttpRequest();E.open("GET",C,true);E.withCredentials=true;E.onreadystatechange=function(){if(E.readyState===4){var F=t(E.response);B.events.push({name:"xhrLoaded",source:B.iframe.contentWindow,data:F,postMessageTimestamp:h(),receivedTimestamp:h()})}};E.send()},w=function(B,C){var D=d+"/audiencenetwork/xhriframe/?sdk=5.5.web";for(var E in C)if(typeof C[E]!=="function")D+="&"+E+"="+encodeURIComponent(C[E]);var F=b.createElement("iframe");F.src=D;F.style.display="none";b.body.appendChild(F);C.iframe=F;C.iframeData={};C.tagJsIframeAppendedTime=h()},x=function(B){var C=function(event){try{var E=event.data;if(E.name in f)B.events.push({name:E.name,source:event.source,data:E.data})}catch(A){}},D=B.iframe.contentWindow.parent;D.addEventListener("message",C,false)},y=function(a){if(a.context&&a.context.sourceUrl)return true;try{return!!JSON.parse(decodeURI(a.name)).ampcontextVersion}catch(A){return false}},z=function(B){var C=h(),D=l()[0],E=j().parentElement,F=D!=a.top,G=D.$sf&&D.$sf.ext,H=o(D),I=m();p(I);var J={amp:y(D),events:[],tagJsInitTime:C,rootElement:E,iframe:null,tagJsIframeAppendedTime:I.iframeAppendedTime||0,url:H,domain:s(),channel:n(o(D)),width:screen.width,height:screen.height,pixelratio:a.devicePixelRatio,placementindex:I.ads.length,crossdomain:F,safeframe:!!G,placementid:g.placementid,format:g.format||"300x250",testmode:!!g.testmode,onAdLoaded:g.onAdLoaded,onAdError:g.onAdError};if(g.bidid)J.bidid=g.bidid;if(F||!c.ancestorOrigins)w(I,J);else{u(I,J,D.document);v(J)}x(J);J.rootElement.dataset.placementid=J.placementid;I.ads.push(J)};try{z()}catch(A){i(A.message||A);throw A}})(window,document,location);    
      </script>    
    </div>    
  </body>
</html>

For units served via DFP but injected into the page by Prebid.js, set the DFP line-item's creative to:

<script>
var w = window;for (i = 0; i < 10; i++) {
w = w.parent;
  if (w.pbjs) {
    try {
      w.pbjs.renderAd(document, '%%PATTERN:hb_adid%%');
      break;
    } catch (e) {
      continue;
    }
  }
}
</script>

3. Raw Request / Response details

Sizes

We support 300x250 banner placements for Header Bidding.

Batching

A single call can request bids for multiple placements. Each placement needs to provide an ID and a format. Placement IDs should be unique within a single request unless you have Lazy Loading implemented on your site. Viewability for each Placement ID is taken into account when determining the bid request so keeping these unique is extremely important for optimal performance.

Example of Raw Request

Here's an example of a raw request with three 300x250 banner placements using different Placement IDs for each slot on the page:

https://an.facebook.com/v2/placementbid.json?
sdk=5.5.web&/* placement 1 */
placementids[]=123_456&
adformats[]=300x250&/* placement 2 */
placementids[]=123_567&
adformats[]=300x250/* placement 3 */
placementids[]=123_678&
adformats[]=300x250

Response

We return a JSON object describing the bids.

  • errors holds a list of error messages, e.g. missing mandatory parameters, disabled placement, etc. Where the error affects a single placement only, it will be prefixed with the placement ID.
  • bids holds a map of placement ID to an array of bids (there may not be entries for all placement IDs or all bids requested per placement ID, e.g. for no bid).
  • Within each bid,
    • bid_id is a token needed to later fetch and display the ad associated with this bid. The token is valid for 10 minutes.
    • bid_price_cents is the bid value, in USD cents, on a CPM basis. E.g. if the bid is 711.2, then the CPM is $7.11, and we will pay 0.7 cents for this single impression.
{
  "errors": [
  ],
  "bids": {
    "123_456": [
      {
        "placement_id": "123_456",
        "bid_id": "12345_67890",
        "bid_price_cents": 321.96265882399,
        "bid_price_currency": "usd",
        "bid_price_model": "cpm"
      },
    ],
    "123_789": [
      {
        "placement_id": "123_789",
        "bid_id": "12345_09876",
        "bid_price_cents": 711.28405256274,
        "bid_price_currency": "usd",
        "bid_price_model": "cpm"
      },
      {
        "placement_id": "123_789",
        "bid_id": "12345_60987",
        "bid_price_cents": 702.34565789214,
        "bid_price_currency": "usd",
        "bid_price_model": "cpm"
       },
    ],
  },
}

No Bids

We may choose not to bid on any request. Common reasons include not being able to match the user to a Facebook account, or the page not running in a mobile browser.

4. Testing and Troubleshooting

  • You can pass the ?anhb_testmode=true query string on the URL to force a dummy bid (with 9999 CPM) and fill
  • You can pass the ?pbjs_debug=true query string on the URL to print auction info to console
  • Prebid.js & Audience Network Console Log Tool - Prints a clean representation of the Auction to Console