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
The steps can be summarized as the following:
- Call
getInterestGroupAdAuctionData()
to get the encrypted payload from the browser - Call
fetch('https://your-ad-server.example')
and send the unified auction request with the encrypted payload to your SAS - Call your SFE's
SelectAd()
operation from your SAS to run the B&A auction - Return the B&A auction result to the page along with the hash of the response
- 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
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 |
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.
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
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 whenrunAdAuction()
is called to finish the auction in the browser. - The origin of the fetch request must match the
seller
origin provided to thegetInterestGroupAdAuctionData()
andrunAdAuction()
calls.
The body of the call contains:
- The plaintext contextual auction payload to be used by SAS to run the contextual auction.
- 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
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:
Accept-Language
User-Agent
- IP address
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
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 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
- For a deeper understanding, see the following explainers on GitHub:
- Learn more about the B&A architecture for web
- Experiment with Protected Audience with B&A by following the End-to-End Local Testing codelab.
Have questions?
- If you have a question about Bidding and Auction Services, open an issue in the B&A Services repository.
- If you have a question about Privacy Sandbox in general, open an issue in the privacy-sandbox-dev-support repository.