Buyer guide: join interest groups and generate bids
Buyer API guide and references to join remarketing lists and bid in FLEDGE auctions.
In this article, you'll find a technical reference for interest groups, as used in the current iteration of the experimental FLEDGE API.
Read the developer guide for the full life cycle of FLEDGE, and refer to the FLEDGE explainer for an in-depth proposal of how browsers record interest groups.
Not a developer? Refer to the FLEDGE overview.
FLEDGE interest groups
A FLEDGE interest group represents a group of people with a common interest, corresponding to a remarketing list. Every FLEDGE interest group has an owner.
Interest group owners act as the buyer in the FLEDGE ad auction. Interest group membership is stored by the browser, on the user's device, and is not shared with the browser vendor or anyone else.
Bid in a FLEDGE ad auction
Owners of FLEDGE interest groups can be invited to bid in FLEDGE ad auctions.
API functions
joinAdInterestGroup()
The advertiser's demand-side platform (DSP) or the advertiser itself calls navigator.joinAdInterestGroup()
to ask the browser to add an interest group to the browser's membership list.
The origin of the calling context for joinAdInterestGroup()
must match the interest group owner's origin, so joinAdInterestGroup()
will need to be called from an iframe (for example, from a DSP) unless the origin of the interest group owner matches the origin of the current document (for example, a website with its own interest groups).
joinAdInterestGroup()
requires permission from:
- The site being visited
- The interest group owner
This means it's not possible for malicious.example
to call joinAdInterestGroup()
for an interest group owned by dsp.example.com
, without dsp.example.com
granting permission.
Permission from the visited site
Permission can be granted from the same origin or cross-origin.
By default, permission is granted for joinAdInterestGroup()
calls from the same origin as the site visited, (in other words, from the same origin as the top-level frame of the current page). Sites can use the join-ad-interest-group
permissions policy header to disable joinAdInterestGroup()
calls.
Calling joinAdInterestGroup()
cross-origin (origins that are different from the current page) can only succeed if the site being visited has set a permissions policy that allows calls to joinAdInterestGroup()
from cross-origin iframes.
The default in the current implementation of FLEDGE is to allow calls to joinAdInterestGroup()
from anywhere in a page, even from cross-origin iframes.
In the future, once site owners have had time to adjust their permissions policies, the plan is by default to disallow calls from cross-origin iframes.
Permission from the interest group owner
Interest group owner permission is implicitly granted by calling joinAdInterestGroup()
from an iframe with the same origin as that of the interest group's owner. For example, a dsp.example.com
iframe can call joinAdInterestGroup()
for interest groups owned by dsp.example.com
.
In essence, joinAdInterestGroup()
can run in a page or iframe on the owner's domain, or be delegated to other domains provided using a list at a .well-known
URL.
Example usage
Here's an example of how one might define an interest group and ask the browser to join the group.
const interestGroup = {
owner: 'https://dsp.example',
name: 'custom-bikes',
biddingLogicUrl: ...,
biddingWasmHelperUrl: ...,
dailyUpdateUrl: ...,
trustedBiddingSignalsUrl: ...,
trustedBiddingSignalsKeys: ['key1', 'key2'],
userBiddingSignals: {...},
ads: [bikeAd1, bikeAd2, bikeAd3],
adComponents: [customBike1, customBike2, bikePedal, bikeFrame1, bikeFrame2],
};
navigator.joinAdInterestGroup(interestGroup, 7 * kSecsPerDay);
The interestGroup
object passed to the function must be no more than 50 kiB in size, otherwise the call will fail. The second parameter specifies the duration of the interest group, capped at 30 days. Successive calls overwrite previously stored values.
All URLs used as parameters for FLEDGE API methods must be from secure origins: all resources must be served over HTTPS URLs. How to use HTTPS for local development explains how to do this when running FLEDGE locally.
In addition, biddingLogicUrl
, decisionLogicUrl
, and trustedBiddingSignals
require an X-Allow-FLEDGE: true
HTTP response header.
Required properties
The only required properties for interest groups are owner
and name
:
Property | Example | Role |
---|---|---|
owner | https://dsp.example | Origin of the interest group owner. |
name | custom-bikes | Name of the interest group. |
Optional properties
The remaining properties are optional:
Property | Example | Role |
---|---|---|
biddingLogicUrl 1, 2 | https://dsp.example/bid/custom-bikes/bid.js | URL for bidding JavaScript run in worklet. |
biddingWasmHelperUrl 1, 2 | https://dsp.example/bid/custom-bikes/bid.wasm | URL for WebAssembly code driven from biddingLogicUrl . |
dailyUpdateUrl 2 | https://dsp.example/bid/custom-bikes/update | URL that returns JSON to update interest group attributes. (See Update the interest group.) |
trustedBiddingSignalsUrl 2 | https://dsp.example/trusted/bidding-signals | Base URL for key-value requests to bidder's trusted server. |
trustedBiddingSignalsKeys | ['key1', 'key2' ...] | Keys for requests to key-value trusted server. |
userBiddingSignals | {...} | Additional metadata the owner can use during bidding. |
ads 1 | [bikeAd1, bikeAd2, bikeAd3] | Ads that might be rendered for this interest group. |
adComponents | [customBike1, customBike2, bikePedal, bikeFrame1, bikeFrame2] | Components for ads composed of multiple pieces. |
Update attributes
dailyUpdateUrl
specifies a web server that returns JSON defining interest group properties, corresponding to the interest group object passed to joinAdInterestGroup()
.
This allows the group's owner to periodically update the attributes of the interest group. In the current implementation, the following attributes can be changed:
biddingLogicUrl
biddingWasmHelperUrl
trustedBiddingSignalsUrl
trustedBiddingSignalsKeys
ads
priority
Any field not specified in the JSON will not be overwritten—only fields specified in the JSON get updated—whereas calling navigator.joinAdInterestGroup()
overwrites any existing interest group.
Updates are best-effort, and can fail under the following conditions:
- Network request timeout (currently 30 seconds).
- Other network failure.
- JSON parsing failure.
Updates are rate-limited to a maximum of one per day.
Updates can be canceled if too much contiguous time has been spent updating, though this doesn't impose any rate limiting on canceled (remaining) updates. Updates that fail due to network errors are retried after an hour, and updates that fail due to disconnection from the internet are retried immediately on reconnection.
Manual updates
Updates to interest groups owned by the current frame's origin can be triggered manually via navigator.updateAdInterestGroups()
.
Rate limiting prevents updates from happening too frequently: repeated calls to navigator.updateAdInterestGroups()
don't do anything until the rate limit period (currently one day) has passed.
The rate limit gets reset if navigator.joinAdInterestGroup()
is called again for the same interest group owner
and name
.
Automatic updates
All interest groups loaded for an auction are updated automatically after an auction completes, subject to the same rate limits as manual updates.
For each owner with at least one interest group participating in an auction, it's as if navigator.updateAdInterestGroups()
is called from an iframe whose origin matches that owner.
Specify ads for an interest group
ads
and adComponents
objects include a URL for an ad creative and, optionally, arbitrary metadata that can be used at bidding time.
For example:
{
renderUrl: 'https://cdn.example/.../bikeAd1.html',
metadata: bikeAd1metadata // optional
}
generateBid()
The interest group owner's script at biddingLogicUrl
must include a generateBid()
function.
When a seller calls navigator.runAdAuction()
, the generateBid()
function is called once for each candidate ad. In other words, it's called for each interest group that the browser is a member of—if the interest group's owner is invited to bid.
The seller provides a decisionLogicUrl
in the auction configuration parameter passed to navigator.runAdAuction()
. The code at this URL must include a scoreAd()
function, which scores the bid generated by each participating bidder.
The biddingWasmHelperUrl
property is optional. It allows the bidder to provide computationally-expensive subroutines in WebAssembly, rather than JavaScript, to be driven from the JavaScript function provided by biddingLogicUrl
.
If provided, it must point to a WebAssembly binary, delivered with an application/wasm mimetype
. The corresponding WebAssembly.Module
is made available by the browser to the generateBid()
function.
The script at biddingLogicUrl
provided by a buyer must include a generateBid()
function.
This function is called once for each candidate ad. runAdAuction()
individually checks each ad, along with its associated bid and metadata, then assigns the ad a numerical desirability score.
generateBid(interestGroup, auctionSignals, perBuyerSignals,
trustedBiddingSignals, browserSignals) {
...
return {
ad: adObject,
bid: bidValue,
render: renderUrl,
adComponents: [adComponentRenderUrl1, ...]
};
}
Arguments
generateBid()
takes the following arguments:
Argument | Role |
---|---|
interestGroup | An object passed to by the ad buyer. The interest group may be updated with dailyUpdateUrl . |
auctionSignals | A property of the auction config argument passed to navigator.runAdAuction() by the seller. This provides information about page context (such as the ad size and the publisher ID), the type of auction (first-price or second-price), and other metadata. |
perBuyerSignals | A property of the auction config argument passed by the seller. This can provide contextual signals from the buyer's server about the page, if the seller is an SSP which performs a real-time bidding call to buyer servers and pipes the response back, or if the publisher page contacts the buyer's server directly. If so, the buyer may wish to check a cryptographic signature of those signals inside generateBid() as protection against tampering. |
trustedBiddingSignals | An object whose keys are the trustedBiddingSignalsKeys for the interest group, and whose values are returned in the trustedBiddingSignals request. |
browserSignals 3 | An object constructed by the browser, which might include information about page context (such as the hostname of the current page, which the seller could otherwise fake) and data for the interest group itself (such as a record of when the group previously won an auction, to allow on-device frequency capping). |
3 The browserSignals
object has the following properties:
{
topWindowHostname: 'publisher.example',
seller: 'https://ssp.example',
joinCount: 3,
bidCount: 17,
prevWins: [[time1,ad1],[time2,ad2],...],
wasmHelper: ... /* WebAssembly.Module object based on interest group's biddingWasmHelperUrl. */
dataVersion: 1, /* Data-Version value from the buyer's Key/Value service response(s). */
}
To calculate a bid
value, code in generateBid()
can use the properties of the function's parameters.
For example:
function generateBid(interestGroup, auctionSignals, perBuyerSignals,
trustedBiddingSignals, browserSignals) {
return {
...
bid: auctionSignals.is_above_the_fold ? perBuyerSignals.atf_value : perBuyerSignals.btf_value,
...
}
}
generateBid()
returns an object with four properties:
Property | Role |
---|---|
ad | Arbitrary metadata about the ad, such as information the seller expects to learn about this bid or ad creative. The seller uses this information in its auction and decision logic. |
bid | A numerical bid that will enter the auction. The seller must be in a position to compare bids from different buyers, therefore bids must be in some seller-chosen unit (such as"USD per thousand"). If the bid is zero or negative, then this interest group will not participate in the seller's auction at all. With this mechanism, the buyer can implement any advertiser rules for where their ads may or may not appear. |
render | A URL, or a list of URLs, that will be used to render the creative if this bid wins the auction. The value has to match the `renderUrl` of one of the ads defined for the interest group. Ads Composed of Multiple Pieces explainer |
adComponents | An optional list of up to 20 components for ads composed of multiple pieces, taken from the adComponents property of the interest group argument passed to `navigator.joinAdInterestGroup()`. |
leaveAdInterestGroup()
The interest group owner can request to a browser be removed from an interest group. The browser removes the interest group from its membership list.
navigator.leaveAdInterestGroup({
owner: 'https://dsp.example',
name: 'custom-bikes'
});
If a user returns to the site which asked the browser to add an interest group, the interest group owner can call the navigator.leaveAdInterestGroup()
function to request the browser remove the interest group.
Code for an ad can also call this function for its interest group.
Frequently asked questions
How do I implement frequency control by click?
How do I implement frequency control by click?
For simple frequency control, you can use the prevWins
field in browserSignals
inside generateBid()
. Alternatively, you can call navigator.leaveAdInterestGroup()
to request that a user's browser leave an interest group when an ad is clicked. This prevents future bidding and acts as a form of frequency capping.
You can also use a first-party cookie to store click information. When the ad is rendered, overwrite an existing interest group with the click data as user bidding signals. The workflow would look something like:
- User visits
advertiser.com/product
. - The advertiser writes "0 clicks" in a first-party cookie and calls
joinAdInterestGroup({ ..., userBiddingSignals: { clicks: [] } })
. - User clicks on an ad at a later time and is taken to
advertiser.com/product
. - The advertiser reads and increments first-party cookie click data, then calls
joinAdInterestGroup({ userBiddingSignals: { clicks: ["1667499972"] } })
. - For future bidding, the click data available in
userBiddingSignals
can be used in bidding logic.
How do I use a user's recent browsing history for ad recommendations?
How do I use a user's recent browsing history for ad recommendations?
A user's browsing history for the site that called joinAdInterestGroup()
can be updated in userBiddingSignals
, which can be used during on-device bidding. See the product-level TURTLEDOVE original proposal which includes some analysis by RTB House on the impact of core metrics for recommendation use case adoption.
dailyUpdateUrl
provides a mechanism to periodically update the attributes of the interest group, but this update is not based on the user's browsing history.
What's the maximum number of interest groups per group owner for a single user?
What's the maximum number of interest groups per group owner for a single user?
Chrome allows up to 1000 interest groups per owner, and up to 1000 interest group owners. These limits are meant as guard rails, not to be hit in regular operation.
All FLEDGE API references
API reference guides are available:
- Developer guide for the FLEDGE API.
- Ad buyer guide to FLEDGE interest groups and bid generation.
- Ad seller guide to FLEDGE ad auctions.
- Guide to reporting auction results
- Best practices for FLEDGE ad auction latency
- Troubleshoot FLEDGE
The FLEDGE API explainer also provides detail about feature support and constraints.