/find

How to choose your Prebid.js timeout from data, not guesswork

Sharad Yadav
Founder, BidCliq

Most Prebid.js timeouts are set once, copied from a setup guide, and never touched again. That is a problem, because the timeout decides two things at once: how many bids reach the ad server and how late the ad request goes out. The right number comes from your own bidder response-time data, measured per device and region, and it is usually lower than the one you have now.

This guide covers what the timeout actually controls in Prebid.js 11 (11.44 is the current release as we write), the settings around it, and a repeatable way to pick a number.

What does bidderTimeout actually control?

bidderTimeout is the global auction timeout in milliseconds. If you never set it, Prebid.js uses 3,000 ms. That default lives in src/config.ts, and it is far higher than what most publishers should run.

pbjs.setConfig({ bidderTimeout: 1000 });

// or per auction, which overrides bidderTimeout for this call only
pbjs.requestBids({ timeout: 800, adUnitCodes: ['div-top'], bidsBackHandler: sendAdserverRequest });

Four things happen when the timer fires:

  1. Prebid emits auctionTimeout, then bidTimeout (an array of the bid requests that did not answer), then auctionEnd.
  2. Your bidsBackHandler runs with whatever bids arrived. Its second argument, timedOut, is true.
  3. Outstanding bidder requests are aborted, unless you set disableAjaxTimeout: true.
  4. Adapters that implement onTimeout are notified, so the SSP can learn it was too slow.

The value also leaves the page. Adapters built on Prebid’s ORTB converter send your timeout to the SSP as OpenRTB tmax. A smaller number tells bidders how much time they really have.

One caveat from the Prebid docs: the timer is a JavaScript setTimeout. On a busy page it can fire later than you asked, so the observed auction length will run a little longer than the setting.

Why the timeout is not the whole wait

The bidderTimeout clock starts when Prebid sends the bid requests, not when you call requestBids. Everything that runs before the requests go out adds to the delay before the ad server call:

  • Consent: the TCF module waits up to consentManagement.gdpr.timeout for an already-found CMP (default 10,000 ms), plus actionTimeout if you set one.
  • User IDs: userSync.auctionDelay (default 0).
  • RTD modules: realTimeData.auctionDelay (default 0).
  • Dynamic floors: floors.auctionDelay (default 0).
  • Currency rates: currency.auctionDelay (default 0).
  • Viewability: in Prebid 11, if your build includes adapters that use Prebid’s viewability helper, the auction waits for IntersectionObserver measurements before it starts. Setting auctionOptions.viewabilityMeasurement: 'boundingBox' removes that wait at the cost of less accurate viewability.

Prebid’s timeout troubleshooting page says to measure the time between auction start and bid requests going out. If it is a sizeable part of the total, cut back how long the CMP, ID and RTD modules may take. Time spent there collects no bids by itself, so it deserves the same scrutiny as bidder time.

Is there a timeoutBuffer setting?

No. We checked the current Prebid.js source and docs and there is no timeoutBuffer key. The buffers that do exist sit in other layers:

LayerSettingWhat it does
FailsafeYour own setTimeout around the ad server callCalls GPT even if Prebid fails to load or answer. Prebid’s FAQ suggests 3,000 ms or less.
AuctionbidderTimeout or requestBids({ timeout })How long Prebid waits for bids once requests are sent. FAQ starting point: 1,000 ms or less.
Prebid Servers2sConfig.timeoutSent to PBS as tmax. Defaults to 75% of the auction timeout, capped by s2sConfig.maxTimeout (1,500 ms in the current source). Prebid’s timeouts page suggests 50 to 75% of the auction timeout.
PBS internalHost settings such as PBS-Go tmax_adjustmentsThe server trims its own bidder deadline so the response gets back to the browser in time.

The failsafe is the one people forget. Prebid’s basic example guards the ad server call so it runs once, from whichever path fires first:

var FAILSAFE_TIMEOUT = 3000;

function initAdserver() {
  if (pbjs.initAdserverSet) return;
  pbjs.initAdserverSet = true;
  googletag.cmd.push(function () {
    pbjs.que.push(function () {
      pbjs.setTargetingForGPTAsync();
      googletag.pubads().refresh();
    });
  });
}

pbjs.que.push(function () {
  pbjs.requestBids({ bidsBackHandler: initAdserver });
});

setTimeout(initAdserver, FAILSAFE_TIMEOUT); // well above the auction timeout

Keep the failsafe clearly above the pre-auction wait plus the auction timeout. If it fires during normal auctions, it is acting as your real timeout and Prebid bids are being dropped.

How do you pick the number from data?

1. Log response times per bidder

Listen to the events Prebid already emits, or use an analytics adapter that does:

pbjs.onEvent('bidResponse', function (bid) {
  log({ bidder: bid.bidder, ms: bid.timeToRespond, cpm: bid.cpm, unit: bid.adUnitCode });
});
pbjs.onEvent('bidTimeout', function (timedOut) {
  timedOut.forEach(function (b) { log({ bidder: b.bidder, timedOut: true, unit: b.adUnitCode }); });
});

Segment by device type, country and page template. A bidder that answers in 250 ms on desktop in Mumbai can look very different on mobile networks elsewhere.

2. See past your current timeout

Your logs only show bids that beat the current timeout. Anything slower is cut off, so you cannot tell whether waiting longer would pay. Run a small sample of pageviews in one of two ways:

  • Longer timeout: give the sample a generous timeout and record everything. This does delay ads for those users, so keep the sample small.
  • Observe without waiting: set disableAjaxTimeout: true for the sample. Requests are no longer aborted, and late responses still fire bidResponse after auctionEnd. They are too late for that ad server call, but you see the tail.

3. Draw the curve

For each candidate timeout (for example every 100 ms from 300 to 2,000 ms), work out what share of bid value would have arrived in time. Use the winning or highest bid per impression rather than all bids, since that is what reaches the ad server. The curve rises fast and then flattens. The flat part is waiting that buys little.

4. Price the waiting

Every extra 100 ms delays the ad request for every impression, not just the ones a slow bidder would have won. That shows up as later ad renders, lower viewability on above-the-fold slots, and fewer impressions from users who leave quickly. Your curve shows what the extra time buys. Your page and viewability data show what it costs.

5. Split test, then repeat

Pick two or three timeouts near the knee of the curve and run them as a split. Judge on total revenue per pageview, including Google Ad Manager and AdX, plus viewability and your Core Web Vitals. Prebid revenue alone will always favour the longest timeout. Repeat the exercise when you add a bidder or move bidders server-side.

What if one bidder is always slow?

Prebid.js has one timeout per auction. There is no per-bidder setting. You have four levers:

  • auctionOptions.secondaryBidders: the auction stops waiting for the listed bidders. It closes once all the other bidders have answered, and a secondary bidder that has not answered by then misses that ad server call. Note that a secondary bidder missing an early close is not reported in bidTimeout, so track it through late bidResponse events. If every bidder is listed, Prebid ignores the setting.
  • Move it to Prebid Server, where s2sConfig.timeout and the host’s tmax rules apply. Prebid’s docs note there is no server-side equivalent of secondaryBidders.
  • Stop calling it where it is slow. Prebid’s timeouts page points to geography as a common cause of bidder-specific timeouts.
  • Talk to the bidder. Timeout notifications and tmax give them the data to fix it.
pbjs.setConfig({
  bidderTimeout: 1000,
  auctionOptions: { secondaryBidders: ['slowBidder'] }
});

Lazy-loaded slots and refresh

Because requestBids takes a timeout per call, lazy-load and refresh auctions do not have to share the page-load value. What matters for a lazy slot is that the auction starts early enough. The fetch margin has to cover the pre-auction wait, the timeout and the ad server round trip, or the slot reaches the viewport before the ad does. In GPT, margins are set with the lazyLoad page setting (fetchMarginPercent, renderMarginPercent, mobileScaling). enableLazyLoad() is now marked deprecated.

If you run one auction at page load and render below-the-fold slots later, bids can age out. minTargetedBidCacheTTL keeps targeted bids in Prebid’s memory, but it does not extend the SSP’s own bid TTL.

For rule-based timeouts, the timeoutRtdProvider module adds milliseconds to bidderTimeout based on device type, connection speed, number of ad units and whether video is present. The rules can be static or fetched from an endpoint.

A note on page speed

The auction runs asynchronously, so a long timeout mostly delays ads, not your content. The CPU work around it still matters. Building and parsing requests for many adapters can create long tasks. Since 11.41, auctionOptions.yield: true yields the main thread between adapter calls in browsers that support scheduler.yield. Do not set it on older versions: they reject the whole auctionOptions object when it contains an unknown key.

Checklist

  • Set bidderTimeout explicitly. The 3,000 ms default is a fallback, not a recommendation.
  • Measure and cap the pre-auction wait: consent, ID, RTD, floors and currency auctionDelay values.
  • Keep a failsafe timeout above pre-auction plus auction time, and alert if it fires often.
  • Keep s2sConfig.timeout below the auction timeout.
  • Log timeToRespond and bidTimeout per bidder, device and country.
  • Sample past your current timeout before deciding that more time won’t pay.
  • Split test two or three values on total revenue per pageview and viewability.
  • Handle chronic laggards with secondaryBidders, Prebid Server or geo targeting, not a higher global timeout.

At BidCliq we repeat this exercise whenever a publisher’s bidder mix changes. A timeout that was right last quarter often isn’t right today.

Frequently asked questions

What is the default Prebid.js timeout?

The default bidderTimeout in Prebid.js is 3,000 ms. Prebid's FAQ recommends 1,000 ms or less for the auction timeout as a starting point, plus a failsafe timeout of 3,000 ms or less outside Prebid.

Does bidderTimeout include the time spent waiting for consent and user IDs?

No. The auction timer starts when Prebid sends the bid requests. Waits for the CMP, user ID modules, RTD modules, dynamic floors and currency rates all happen before that, so they add to the total delay before the ad server call.

Can I set a different timeout for one bidder in Prebid.js?

No, Prebid.js has one timeout per auction. For a slow bidder you can list it in auctionOptions.secondaryBidders so the auction does not wait for it, move it to Prebid Server, or stop calling it where it is slow.

How do I see which bidders time out?

Listen for the bidTimeout event, which carries the bid requests that did not answer in time, and log timeToRespond from bidResponse events. Most analytics adapters collect both.

Should lazy-loaded slots use the same timeout?

Not necessarily. pbjs.requestBids accepts a timeout per call, so a lazy-load or refresh auction can use its own value. What matters is that the fetch margin covers the pre-auction wait, the timeout and the ad server round trip.

Sharad Yadav

About the author

Founder, BidCliq

Sharad Yadav is the founder of BidCliq, a publisher-first ad monetization platform that helps publishers and ad ops teams run header bidding, Prebid and programmatic across display, video, app and CTV. Before BidCliq, he led AdTech and product at GeeksforGeeks, where he built an in-house ads product that doubled programmatic revenue within nine months and an anti-adblock solution that lifted ad yield by 10%. He is a founding member of AdCP.