Understanding Net-Bidding Mechanics, Bid Adjustments, and Desirability Scoring in Prebid.js 11.x
A $5.00 CPM bid returned by a header bidding adapter operating on a gross contractual basis and a $5.00 CPM bid returned by Google Ad Exchange (AdX) or a

A $5.00 CPM bid returned by a header bidding adapter operating on a gross contractual basis and a $5.00 CPM bid returned by Google Ad Exchange (AdX) or a net-settled SSP adapter do not represent equal clearing value in a unified first-price auction. When an unadjusted gross bid competes directly against net demand inside Google Ad Manager (GAM 360), the gross bidder wins impressions using pre-commission currency while settling invoices in post-commission currency. Across high-volume Web, Mobile App, and Connected TV (CTV) supply, failing to normalize gross bids and scale outgoing floor prices creates a silent yield leak that depresses effective CPMs (eCPMs) and distorts supply path optimization (SPO) signals for buyers.
In client-side and server-side header bidding architectures, auction parity requires two symmetrical operations: discounting incoming gross bid responses to reflect true net publisher revenue, and inflating outgoing bid floors so gross-bidding demand partners receive the exact minimum threshold required to clear the auction after their contractual fee is deducted. However, adjusting the raw CPM of a bid to account for secondary operational factors (such as render rate discrepancies, working capital costs across different payment windows, or adapter timeout behaviors) historically introduced a secondary problem. Because Prebid.js derives ad server targeting keys (hb_pb) directly from the adjusted bid CPM, using bidCpmAdjustment for non-commission ranking penalties artificially depressed the price bucket sent into GAM, allowing AdX or Open Bidding to clear impressions at a lower price than the header bidding partner was actually willing to pay.
With the release series of Prebid.js 11.x, publishers and AdOps engineers gain granular primitives inside pbjs.bidderSettings that separate contractual gross-to-net normalization from internal wrapper ranking and special bid eligibility. By combining bidCpmAdjustment and inverseBidAdjustment with the Prebid 11.x introductions of bidDesirabilityAdjustment, allowPriorityBids, and allowHouseBids, engineering teams can maintain strict financial accuracy in GAM while controlling how bids are prioritized inside the client-side wrapper.
Gross vs. Net Bidding Across Prebid, AdX, and Open Bidding Direct Pay
Every impression entering the Google Ad Manager unified first-price auction is evaluated on a single numerical plane, where line item dynamic allocation compares the declared price of Prebid key-value pairs against real-time bids from AdX and Google Open Bidding. For that comparison to maximize publisher yield, every competing demand channel must express its bid in identical net terms, meaning the exact dollar amount that will land in the publisher's bank account upon settlement.
Within GAM 360, Google natively normalizes its managed demand channels before the unified auction closes:
- Google Ad Exchange (AdX): Buyers submit gross bids into AdX, and Google automatically deducts the configured AdX revenue share prior to comparing the bid against competing Prebid price priority line items or guaranteed roadblocks. If a DSP bids $5.00 and the publisher's AdX revenue share is 80%, the bid competes in the unified auction at $4.00 net.
- Google Open Bidding (Standard): Participating SSPs and exchanges respond to GAM server-to-server requests with a bid value. GAM deducts its Open Bidding platform fee (typically ranging from 5% to 10% depending on inventory format and contract tier) so the remaining value competes on a net basis.
- Open Bidding Direct Pay: Under Google's Open Bidding Direct Pay framework, contractual settlement and payout pathways can be routed directly between the participating exchange partner and the publisher while preserving server-side unified auction mechanics. Even when cash settlement occurs outside Google's standard disbursement ledger, the bid value entering the GAM auction must still reflect the net payout after all exchange and platform fees are calculated.
Header bidding via Prebid.js and Prebid Server operates outside GAM's native fee-calculation engine. GAM treats the hb_pb targeting key as a literal net value. While our unified monetization platform and many modern tier-1 SSP adapters submit net bids directly in the OpenRTB seatbid.bid[].price field, a subset of participating SSP partners still transmit gross bids where a 15% to 25% rev-share fee is withheld at the end of the billing cycle.
Consider a concrete auction scenario where Partner A submits net bids and Partner B submits gross bids subject to a 20% contractual fee, with no Prebid adjustment applied:
- Partner A bids $4.50 net.
- Partner B bids $4.80 gross (which equals $3.84 net to the publisher after the 20% fee).
- Prebid selects Partner B's $4.80 bid as the wrapper winner and passes
hb_pb = 4.80to GAM. - AdX submits a $4.60 net bid in the unified auction.
- GAM awards the impression to Partner B because $4.80 exceeds $4.60.
In this single transaction, the publisher loses $0.76 of real revenue ($3.84 actual net realized from Partner B versus the $4.60 net AdX bid that was blocked, or $0.66 lost compared to Partner A's $4.50 net bid). Furthermore, impression discrepancy rates between the publisher's Prebid analytics (bidWon events) and the partner's billable ad server logs compound this loss. At Sky Boosts, whether operating through Google MCM (Manage Inventory or Manage Account), Prebid client-side wrappers, or server-to-server OpenRTB 2.6 endpoints, all bids are evaluated alongside strict HUMAN and Pixalate IVT-clean traffic filtering and mainstream brand-safe demand rules (zero alcohol, gambling, adult, or tobacco categories), ensuring that net clearing prices match final Net-30 and Net-60 payout statements without end-of-month clawbacks.
Configuring pbjs.bidderSettings: bidCpmAdjustment and inverseBidAdjustment
To eliminate gross-versus-net distortion before bids reach the ad server, Prebid.js provides the pbjs.bidderSettings publisher API, which exposes bidCpmAdjustment to scale incoming gross bids down to net and inverseBidAdjustment to scale outgoing floor prices up by the reciprocal factor.
When an adapter returns a bid response, Prebid.js executes bidCpmAdjustment during the bid normalization lifecycle stage. The function receives the raw bidCpm float and the full bid object, allowing engineering teams to apply a flat contractual multiplier or inspect conditional attributes such as bid.mediaType (for instance, when a partner charges a 15% take rate on display banner inventory but a 20% take rate on video instream inventory).
However, implementing bidCpmAdjustment in isolation breaks floor enforcement in the Prebid Price Floors Module. If a publisher defines a hard net floor of $2.00 CPM in the Price Floors configuration and does not define an inverse function, Prebid passes bidfloor: 2.00 in the outgoing OpenRTB request to a gross bidder who takes a 20% commission. If that bidder responds with a $2.10 gross bid to clear the $2.00 floor they received, bidCpmAdjustment multiplies $2.10 by 0.80, reducing the bid to $1.68 net. The Prebid Price Floors Module then evaluates the adjusted $1.68 net bid against the $2.00 net floor and rejects the bid as a floor violation. The demand partner wasted DSP QPS and computational resources bidding on an impression they believed they could win at $2.10, while the publisher lost fill because the partner was never informed that their gross bid needed to be at least $2.50 ($2.00 / 0.80) to clear the net threshold.
Adding inverseBidAdjustment to pbjs.bidderSettings resolves this asymmetry. When the Price Floors Module prepares the bid request for a specific bidder, it passes the target net floor value and the bidRequest object into inverseBidAdjustment. Returning val / 0.80 instructs Prebid to transmit a $2.50 floor in the outgoing bid request to that gross partner, guaranteeing that any bid meeting the transmitted floor will still equal or exceed $2.00 after bidCpmAdjustment scales it down on return.
New in Prebid 11.x: bidDesirabilityAdjustment (11.15), allowPriorityBids (11.44), and allowHouseBids (11.45)
While bidCpmAdjustment and inverseBidAdjustment solve contractual rev-share math, AdOps teams frequently need to weight bids based on operational performance metrics that are not deducted from the invoice price. For example, a publisher receiving reliable Net-30 payouts from one SSP and slow Net-90 payouts from another carries working capital financing costs on the slower partner. Similarly, an adapter whose creative payloads exhibit higher browser main-thread latency or a 4% render drop-off between bidWon and GAM viewability measurement is less desirable at an identical net CPM.
Prior to Prebid.js 11.x, teams tried to account for these factors inside bidCpmAdjustment by applying an extra 5% to 8% haircut. Doing so mutated bid.cpm, which lowered the hb_pb price bucket sent to GAM. If a net bidder submitted a $4.00 bid and was penalized by 10% inside bidCpmAdjustment for slow payment terms, hb_pb became 3.60. In GAM, an AdX bid of $3.75 would then beat the Prebid bidder, costing the publisher $0.25 in actual revenue because the Prebid bidder was genuinely willing to pay $4.00 net.
Prebid.js 11.x addresses this architectural flaw alongside two governance flags in pbjs.bidderSettings:
bidDesirabilityAdjustment(added in Prebid.js v11.15.0): This function accepts the already net-adjustedbidCpmand thebidobject, returning a custom numerical desirability score used strictly to rank and sort bids inside Prebid. Crucially,bidDesirabilityAdjustmentdoes not modify the underlyingbid.cpmor the resultinghb_pbkey-value sent to the ad server. If Bidder X submits a $4.00 net bid with a 0.90 desirability multiplier (score3.60) and Bidder Y submits a $3.80 net bid with a 1.00 desirability multiplier (score3.80), Bidder Y wins the internal Prebid auction (whenSend Top Bidmode is active) and passeshb_pb = 3.80to GAM. Conversely, if Bidder X's $4.00 bid (desirability score3.60) faces a $3.50 bid from Bidder Y (desirability score3.50), Bidder X wins the Prebid auction and passes its full, unpenalizedhb_pb = 4.00value into GAM to compete at full strength against AdX.allowPriorityBids(added in Prebid.js v11.44.0): A boolean flag (defaulting tofalse) that governs whether a specific bidder adapter is authorized to mark its incoming bid responses as priority bids capable of bypassing standard CPM or desirability ranking. Restricting this flag to trusted direct or curated demand prevents third-party adapters from arbitrarily elevating lower-CPM bids to the top of the wrapper waterfall.allowHouseBids(added in Prebid.js v11.45.0): A boolean flag (defaulting tofalse) that controls whether a bidder is permitted to submit $0.00 CPM house or backfill bids into the auction. In complex setups where Prebid acts as the primary mediation layer before falling back to house campaigns, keepingallowHouseBids: falseacross commercial SSP adapters prevents misconfigured DSP seats or zero-floor responses from registering as valid $0.00 winning bids.
window.pbjs = window.pbjs || {};
window.pbjs.que = window.pbjs.que || [];
pbjs.que.push(function() {
// Helper to resolve media-type specific net multipliers for a gross bidder
const getGrossToNetFactor = function(mediaType) {
return mediaType === 'video' ? 0.80 : 0.85; // 20% fee on video, 15% on banner
};
pbjs.bidderSettings = {
standard: {
allowPriorityBids: false, // Prebid 11.44.0+ governance default
allowHouseBids: false // Prebid 11.45.0+ blocks unauthorized $0.00 bids
},
grossSspPartner: {
allowPriorityBids: false,
allowHouseBids: false,
// 1. Normalize incoming gross bid to net publisher payout
bidCpmAdjustment: function(bidCpm, bid) {
const factor = getGrossToNetFactor(bid.mediaType);
const rawDiscrepancyFactor = 0.98; // 2% historical render discrepancy
return Number((bidCpm * factor * rawDiscrepancyFactor).toFixed(4));
},
// 2. Scale outgoing Price Floors module thresholds by the exact inverse
inverseBidAdjustment: function(floorVal, bidRequest) {
const isVideo = bidRequest && bidRequest.mediaTypes && bidRequest.mediaTypes.video;
const factor = getGrossToNetFactor(isVideo ? 'video' : 'banner');
const rawDiscrepancyFactor = 0.98;
return Number((floorVal / (factor * rawDiscrepancyFactor)).toFixed(4));
},
// 3. Prebid 11.15.0+: Score bid for wrapper ranking without altering hb_pb
bidDesirabilityAdjustment: function(adjustedNetCpm, bid) {
// Apply a 4% working-capital ranking weight for Net-90 terms vs Net-30 partners
const workingCapitalWeight = 0.96;
// Apply a modest bonus if creative uses lightweight CSAI VAST vs heavy VPAID
const rendererWeight = (bid.mediaType === 'video' && !bid.renderer) ? 1.02 : 1.00;
return Number((adjustedNetCpm * workingCapitalWeight * rendererWeight).toFixed(4));
}
}
};
});
Implementing the OpenRTB 2.6 DurFloor Object for Video and Audio
When inverseBidAdjustment is active across CTV, OTT, and instream video inventory, floor scaling must extend beyond the top-level imp.bidfloor attribute into duration-specific pricing tiers. In CTV server-side ad insertion (SSAI) and client-side VAST 4.3 implementations, a single ad slot within a pod can often accept multiple creative durations, such as 6-second bumpers, 15-second standard spots, and 30-second or 60-second long-form creatives. Enforcing a single static imp.bidfloor of $18.00 CPM across that slot either prices out high-yielding 6-second and 15-second buyers or severely underprices 30-second and 60-second inventory, destroying per-second yield density across the ad break.
OpenRTB 2.6 solves this via the DurFloor object, transmitted inside the durfloors array of the Video (imp.video.durfloors) or Audio (imp.audio.durfloors) objects, or at the deal level (imp.pmp.deals[].durfloors). Each DurFloor object specifies a minimum duration (mindur), a maximum duration (maxdur), and a corresponding minimum CPM (bidfloor). When Prebid.js 11.x or Prebid Server constructs an OpenRTB 2.6 payload for a gross-bidding video partner, the inverseBidAdjustment factor (for example, 0.80 for a 20% video fee combined with 0.98 discrepancy factor, equaling 0.784) must scale both the base imp.bidfloor and every tier inside imp.video.durfloors:
{
"id": "req-ctv-pod-98412",
"imp": [
{
"id": "imp-slot-1",
"bidfloor": 12.7551,
"bidfloorcur": "USD",
"video": {
"mimes": ["video/mp4", "application/x-mpegURL"],
"minduration": 6,
"maxduration": 30,
"protocols": [7, 8, 11, 12, 13, 14],
"w": 1920,
"h": 1080,
"startdelay": 0,
"plcmt": 1,
"maxseq": 4,
"poddur": 90,
"rqddurs": [6, 15, 30],
"durfloors": [
{
"mindur": 1,
"maxdur": 10,
"bidfloor": 12.7551
},
{
"mindur": 11,
"maxdur": 15,
"bidfloor": 17.8571
},
{
"mindur": 16,
"maxdur": 30,
"bidfloor": 25.5102
}
]
}
}
]
}
In the OpenRTB 2.6 payload above, the publisher's internal net floor targets are $10.00 for 6-second spots, $14.00 for 15-second spots, and $20.00 for 30-second spots. Because the gross SSP partner's combined net factor is 0.784, the outgoing durfloors[].bidfloor values are scaled to $12.7551, $17.8571, and $25.5102 respectively. When the DSP responds with a 30-second VAST 4.3 creative at $26.00 gross, bidCpmAdjustment normalizes the response to $20.384 net, cleanly clearing the $20.00 net duration floor while passing hb_pb = 20.30 (under $0.10 high-granularity video buckets) into GAM.
For AdOps and programmatic engineering teams upgrading to Prebid.js 11.x, the immediate action item is an audit of every active pbjs.bidderSettings block. Remove non-contractual penalties (such as payment-term weighting or creative latency adjustments) from bidCpmAdjustment and migrate them into bidDesirabilityAdjustment so your header bidding bids never enter GAM with artificially depressed hb_pb values. Simultaneously, verify that every adapter with a bidCpmAdjustment function has an exact reciprocal inverseBidAdjustment function, and explicitly lock allowPriorityBids and allowHouseBids to false on standard commercial demand to maintain a deterministic, net-accurate unified auction.