Integrate with B&A as a seller

Bidding and Auction (B&A) Services is a set of services for ad buyers and sellers that runs in a Trusted Execution Environment (TEE) to facilitate a Protected Audience (PA) auction. This developer's guide explains how a seller can integrate with a Chrome PA auction for B&A.

Walkthrough

Seller integration flow where the JavaScript code gets the B&A auction payload that is sent to SAS, and SAS forwards the request to the Seller Front-End (SFE). SFE returns the result that SAS should forward to the browser, and the seller JavaScript code calls runAdAuction

The steps can be summarized as the following:

  1. Call getInterestGroupAdAuctionData() to get the encrypted payload from the browser
  2. Call fetch('https://your-ad-server.example') and send the unified auction request with the encrypted payload to your SAS
  3. Call your SFE's SelectAd() operation from your SAS to run the B&A auction
  4. Return the B&A auction result to the page along with the hash of the response
  5. Call runAdAuction() in the browser to run a single-seller, mixed-mode, or multi-seller PA auction, and pass in the server-side B&A auction result to the call

Get encrypted ad auction data

The same walkthrough diagram with the first step highlighted, which is when the seller JavaScript code calls getInterestGroupAdAuctionData

To get the data needed to run the server-side B&A auction, the seller's JavaScript code on the publisher page calls navigator.getInterestGroupAdAuctionData().

const adAuctionData = await navigator.getInterestGroupAdAuctionData({
  seller: 'https://ssp.example', // Required
  requestSize: 51200,
  coordinatorOrigin: 'https://publickeyservice.pa.gcp.privacysandboxservices.com/',
  perBuyerConfig: {
    'https://dsp-x.example': { targetSize: 8192 },
    'https://dsp-y.example': { targetSize: 8192 }
  }
});

const { requestId, request } = adAuctionData;
Field Description
seller Required. Origin of the seller running the auction. This value must match the seller value in the runAdAuction() call later.
requestSize Optional. Sets the maximum payload size of all buyer data. See the request size section of the explainer to learn more.
perBuyerConfig Optional. Sets configurations for each buyer, and also controls which buyers participate in the B&A auction.

If buyer origins are listed in perBuyerConfig, only those buyer interest group data are included in the payload. If no buyers are listed in perBuyerConfig, then all interest groups of the user are included in the payload.

targetSize Optional if requestSize is set. Required if a buyer origin is set in the perBuyerConfig but requestSize isn't set.

Sets the maximum payload size of that buyer's data. See the request size section of the explainer to learn more.

coordinatorOrigin Optional, but will be eventually required. Defaults to https://publickeyservice.pa.gcp.privacysandboxservices.com when not set.

Sets the coordinator to use for fetching the key for encrypting the payload. See the coordinator section of the explainer to learn more.

When the call is made, the browser reads the interest groups of the buyers listed in perBuyerConfig, and encrypts the buyer data. This buyer data contains cross-site information to be used for bidding, and cannot be decrypted outside a TEE. For payload optimization, only the interest group name, trusted bidding signal keys, and browser signals are included in the payload.

In the ad auction data object returned by the getInterestGroupAdAuctionData() call, the requestId string and the encrypted request byte array are available.

Chrome DevTools screenshot that shows request and request ID being available in the ad auction data

The requestId string is used later when runAdAuction() is called to finish the auction in the browser. The encrypted request payload is sent to the Seller Ad Service as a part of the unified auction request.

To see an example of this call, see the seller JavaScript code of the local testing app.

Send the unified auction request to SAS

The same walkthrough diagram with the second step highlighted, which is when the seller JavaScript code sends a unified auction request to SAS

A unified auction request is a request that contains the plaintext contextual auction payload and the PA B&A auction payload. The PA B&A auction payload is the encrypted request data the browser generated in the getInterestGroupAdAuctionData() call. This request is sent to SAS, where the contextual auction and the PA B&A auction are orchestrated.

fetch('https://ssp.example/ad-auction', {
  method: 'POST',
  adAuctionHeaders: true,
  body: JSON.stringify({
    contextualAuctionPayload: { somePayload },
    protectedAudienceAuctionPayload: encodeBinaryData(request)
  }),
});

To send the request to SAS, a fetch() call is made from the page:

  • The call must include the adAuctionHeaders: true option, which signals the browser to verify the response of this call at a later time when runAdAuction() is called to finish the auction in the browser.
  • The origin of the fetch request must match the seller origin provided to the getInterestGroupAdAuctionData() and runAdAuction() calls.

The body of the call contains:

  1. The plaintext contextual auction payload to be used by SAS to run the contextual auction.
  2. The encrypted Protected Audience auction payload to be sent to SFE by SAS to run the server-side B&A auction.

To see an example of this call, see the seller JavaScript code of the local testing app.

Base64 encoding and decoding

The encrypted request payload returned from the getInterestGroupAdAuctionData() call is an instance of Uint8Array, which is a data type that JSON cannot handle. To send the byte array in a JSON format, you can apply a base64 encoding to the binary data to convert it into a string.

The JavaScript browser API provides the atob() and btoa() functions on window that convert between binary data and base64-encoded ASCII string. (atob means ASCII-to-binary, and btoa means binary-to-ASCII).

Call btoa() to encode binary data into a base64-encoded string looks like the following:

function encodeBinaryData(data) {
  return btoa(String.fromCharCode.apply(null, data));
}

The encrypted B&A auction result returned from this fetch call is also in a base64 encoding, so you need to decode it back to binary data. Call atob() to decode the base64-encoded ASCII string to binary data:

function decodeBase64String(base64string) {
  return new Uint8Array(
    atob(base64string)
      .split('')
      .map((char) => char.charCodeAt(0))
  );
}

However, a base64-encoded string is usually about 33% larger than the original data. If you want further latency improvement, use a format other than JSON to send the binary data.

Call SFE's SelectAd to run the B&A auction

The same walkthrough diagram with the third step highlighted, which is when SAS sends a SelectAd request to SFE, and SFE runs a B&A auction

Once the Seller Ad Service receives the unified auction request from the page, the contextual auction runs first to determine the contextual auction winner and to collect the buyer signals to be passed into the PA B&A auction. Then, the B&A auction is initiated by calling the SFE's SelectAd operation from SAS with the request payload. Note that some metadata from the page's request to SAS in step #2 is forwarded to SFE.

Construct the SelectAdRequest payload

The SelectAd call's request payload can be constructed like the following:

const selectAdRequest = {
  auction_config: {
    seller: 'https://ssp.example',
    auction_signals: '{"testKey":"someValue"}',
    seller_signals: '{"testKey":"someValue"}',
    buyer_list: [
      'https://dsp-x.example',
      'https://dsp-y.example',
    ],
    per_buyer_config: {
      'https://dsp-x.example': { buyer_signals: '{"testKey": "someValue"}' },
      'https://dsp-y.example': { buyer_signals: '{"testKey": "someValue"}' },
    },
  },
  client_type: 'CLIENT_TYPE_BROWSER',
  protected_auction_ciphertext: decodeBase64string(request)
};

Note that if the encrypted ad auction data from the browser was base64-encoded, it needs to be decoded back to binary data if the request to SFE is sent using gRPC. If the request is sent using HTTP, then the encrypted ad auction data can stay in its base64-encoded form.

To see other fields defined in the SelectAd request, see the proto definition of SelectAdRequest.

Set top-level seller field for mixed-mode and component auctions

If the seller is running a mixed-mode auction or participating as a component seller in a multi-seller auction, the top_level_seller field needs to be defined in the request.

If you are a mixed-mode seller, the top_level_seller value is your origin:

const selectAdRequest = {
  auction_config: {
    seller: 'https://ssp-mix.example',
    top_level_seller: 'https://ssp-mix.example',
  }
}

If you are a component seller, the top_level_seller value is the multi-seller auction's top-level seller:

const selectAdRequest = {
  auction_config: {
    seller: 'https://ssp-mix.example',
    top_level_seller: 'https://ssp-top.example',
  }
}

Call SFE's SelectAd

The call to SFE from SAS can be made with gRPC or HTTP.

gRPC call

The gRPC request to SFE looks like the following using Express in Node with a gRPC client:

import grpc from '@grpc/grpc-js';

// Load proto definition
const packageDefinition = protoLoader.loadSync(protoPath, { keepCase: true, enums: String });

const {
  privacy_sandbox: {
    bidding_auction_servers: { SellerFrontEnd }
  }
} = grpc.loadPackageDefinition(packageDefinition);

// Instantiate the gRPC client
const sfeGrpcClient = new SellerFrontEnd('192.168.84.104:50067', grpc.credentials.createInsecure());

// Send SelectAd request
sfeGrpcClient.selectAd(selectAdRequest,(error, response) => {
  // Handle SFE response
});

The proto definition for the SFE client can be found in the local testing app repository.

HTTP call to the Envoy proxy

The HTTP POST request to SFE is sent to the /v1/selectAd path, and looks like the following:

fetch('https://ssp-ba.example/sfe/v1/selectAd', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(selectAdRequest),
});

Forward metadata

The following metadata from the page's call to SAS should be added to the SAS's SelectAd call to SFE:

When the metadata are sent to SFE, they must use the following non-standard headers because gRPC may alter the User-Agent header:

  • X-Accept-Language
  • X-User-Agent
  • X-BnA-Client-IP

The following is an example of how the metadata can be forwarded using Express in Node with a gRPC client:

sellerAdService.post('/ad-auction', (req, res) => {
  // …
  const metadata = new grpc.Metadata();
  metadata.add('X-Accept-Language', req.header('Accept-Language'));
  metadata.add('X-User-Agent', req.header('User-Agent'));
  metadata.add('X-BnA-Client-IP', req.ip);

  const sfeGrpcClient = createSfeGrpcClient();
  sfeGrpcClient.selectAd(selectAdRequest, metadata, callbackFn);
})

The following is an example of how the metadata can be forwarded using an HTTP call:

sellerAdService.post('/ad-auction', (req, res) => {
  // …
  fetch('https://ssp-ba.example/sfe/v1/selectAd', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Accept-Language': req.header('Accept-Language'),
      'X-User-Agent': req.header('User-Agent'),
      'X-BnA-Client-IP': req.ip
    },
    body: JSON.stringify(selectAdRequest)
  });
})

Server-orchestrated multi-seller auction

If you are a top-level seller running a server-orchestrated multi-seller auction, the GetComponentAuctionCiphertexts call is made to SFE before the SelectAd call is made. The response contains the re-encrypted component auction payloads that are sent to the component seller ad services. The returned component B&A ad auction results are supplied to the SelectAd call of the top-level seller's SFE.

See the multi-seller explainer on GitHub to learn more.

Return the B&A auction result to the page

The same walkthrough diagram with the fourth step highlighted, which is when SAS sends the SelectAd's auction result back to the browser

After the B&A auction concludes, the encrypted auction result is returned to SAS, and SAS responds to the unified auction request from the page in step #2 with the encrypted auction result. In the SAS response to the page, the base64url-encoded SHA-256 hash of the encrypted auction result is set in the Ad-Auction-Result response header. This hash is used by the browser to verify the payload when finishing the auction in the client.

Creating a SHA-256 hash with base64 encoding looks like the following in Node:

import { createHash } from 'crypto';

createHash('sha256')
  .update(binaryData, 'base64')
  .digest('base64url');

Attaching the hash in the response header and returning the auction result to the page looks like the following:

sellerAdService.post('/ad-auction', (req, res) => {
  // …
  sfeGrpcClient.selectAd(selectAdRequest, metadata, (error, response) => {
    const { auction_result_ciphertext } = response;

    const ciphertextShaHash = createHash('sha256')
      .update(auction_result_ciphertext, 'base64')
      .digest('base64url');

    res.set('Ad-Auction-Result', ciphertextShaHash);

    res.json({
      protectedAudienceAuctionResult: encodeBinaryData(auction_result_ciphertext),
      contextualAuctionResult: getContextualAuctionResult()
    });
  });
})

Since this is a response to the unified auction request made from the page in step #2, the contextual auction result is also included in the response.

Multiple hashes can be included in the Ad-Auction-Result by repeating the header or separating the hashes. The following two response headers are equivalent:

Ad-Auction-Result: ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0=,9UTB-u-WshX66Xqz5DNCpEK9z-x5oCS5SXvgyeoRB1k=
Ad-Auction-Result: ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0=
Ad-Auction-Result: 9UTB-u-WshX66Xqz5DNCpEK9z-x5oCS5SXvgyeoRB1k=

To see an example of this call, see the seller server code of the local testing app.

Call runAdAuction() to complete the auction

The same walkthrough diagram with the fifth step highlighted, which is when the client-side JavaScript code runs the auction and supplies the server response

The unified auction response returned from SAS includes the encrypted B&A auction result. This payload is passed into the runAdAuction() call to finish the auction in the browser. The requestId value from the getInterestGroupAdAuctionData() call in step #1 is also passed into the auction.

// Get the encrypted ad auction data (Step #1)
const { requestId, request } = navigator.getInterestGroupAdAuctionData(adAuctionDataConfig)

// Send unified auction request (Step #2)
const response = await fetch('https://ssp-ba.example/ad-auction', {
  method: 'POST',
  body: JSON.stringify({
    adAuctionRequest: encodeBinaryData(request),
  }),
});

const { protectedAudienceAuctionResult } = await response.json();

// Finish the auction in the browser
await navigator.runAdAuction({
  // pass in "requestId" and "protectedAudienceAuctionResult"
  // the config structure will differ based on the auction configuration
});

The structure of the auction config passed into the runAdAuction() call differs based on the auction configuration chosen by the seller.

Single-seller auction

For running a single-seller B&A auction, the auction config of the runAdAuction() call is constructed like the following:

await navigator.runAdAuction({
  seller: 'https://ssp-ba.example',
  requestId,
  serverResponse: protectedAudienceAuctionResult,
});

The requestId field accepts the requestId returned by the getInterestGroupAdAuctionData() call. The serverResponse field accepts a byte array of the B&A auction that ran in step #3.

To see an example of this call, see the seller JavaScript code of the local testing app.

Mixed-mode auction

For running a mixed-mode B&A auction where both on-device and B&A buyers can participate, the auction config of the runAdAuction() call is constructed like the following:

await navigator.runAdAuction({
  seller: 'https://ssp-mix.example',
  decisionLogicURL: 'https://ssp-mix.example/score-ad.js',
  componentAuctions: [
    // B&A auction result
    {
      seller: 'https://ssp-mix.example',
      requestId,
      serverResponse: protectedAudienceAuctionResult,
    },
    // On-device auction config
    {
      seller: 'https://ssp-mix.example',
      decisionLogicURL: 'https://ssp-mix.example/on-device-score-ad.js',
      interestGroupBuyers: [
        'https://dsp-a.example', // On-device buyer
        'https://dsp-a.example', // On-device buyer
      ],
    },
  ]
});

To facilitate a mixed-mode auction, the B&A auction result and the on-device auction config are passed into the componentAuctions field. In a mixed-mode auction, the seller value is the same for both the top-level config and the component configs.

To see an example of this call, see the seller JavaScript code of the local testing app.

Multi-seller auction

If you are a top-level seller running a device-orchestrated multi-seller auction, then each component seller submits their B&A auction result and the on-device auction configs.

await navigator.runAdAuction({
  seller: 'https://ssp-top.example',
  decisionLogicURL: 'https://ssp-top.example/score-ad.js',
  componentAuctions: [
    // SSP-BA's B&A-only auction result
    {
      seller: 'https://ssp-ba.example',
      requestId: 'g8312cb2-da2d-4e9b-80e6-e13dec2a581c',
      serverResponse: Uint8Array(560) [193, 120, 4, ] // Encrypted B&A auction result
    },
    // SSP-MIX's B&A auction result
    {
      seller: 'https://ssp-mix.example',
      requestId: 'f5135cb2-da2d-4e9b-80e6-e13dec2a581c',
      serverResponse: Uint8Array(560) [133, 20, 4, ] // Encrypted B&A auction result
    }.
    // SSP-MIX's on-device auction config
    {
      seller: 'https://ssp-mix.example',
      interestGroupBuyers: ['https://dsp-a.example', 'https://dsp-b.example'],
      decisionLogicURL: 'https://ssp-mix.example/score-ad.js',
    }
    // SSP-OD's on-device auction config
    {
      seller: 'https://ssp-od.example',
      interestGroupBuyers: ['https://dsp-a.example', 'https://dsp-b.example'],
      decisionLogicURL: 'https://ssp-od.example/score-ad.js',
    }
  ]
})

To see an example of this call, see the seller JavaScript code of the local testing app.

Next steps

After reading this guide, you can take the next following steps:

Learn more

Have questions?