Header bidding troubleshooting.
Seven failure modes, one decision tree: start from the symptom, follow the checks, end at the fix — every command from the primary docs.
Header bidding troubleshooting is the discipline of isolating why a Prebid.js auction fails to produce a rendered, correctly-reported ad: whether requests fire, bidders respond, targeting reaches the ad server, line items win, creatives render, and the numbers reconcile. This page orders those checks into one decision tree.
Last validated July 13, 2026 · Maintained by Evgeny Popov
The official troubleshooting docs are reference-shaped — a list of commands, a list of issues. Diagnosis is a sequence. This page is the sequence.
Fast read
- What it is
- A troubleshooting decision tree for Prebid.js header bidding: six pipeline questions — do requests fire, do bidders respond, does targeting reach the ad server, does the line item win, does the creative render, do the numbers reconcile — plus a parallel Prebid Server check. The first no is your symptom; each symptom section maps observation to cause to fix.
- The method
- One diagnosis at a time, each branch starting from a console observation — pbjs.getBidResponses(), pbjs.getAdserverTargeting(), googletag.openConsole() — rather than a hypothesis. The official docs list the commands and list the issues; the sequence between them is this page.
- The decisive test
- The debugging module’s fake-bid intercept splits every no-bid mystery in two: if an injected $10 bid renders end-to-end, the plumbing works and the gap is demand; if it does not, stop talking to SSPs and fix the pipeline.
- What it is not
- Not an adapter parameter reference (that is the official bidder index), not a Prebid 10 → 11 upgrade debugger (that page exists separately on this site), and not a GAM setup tutorial — it assumes line items exist and asks why they do not serve.
- Best for
- Ad-ops and publisher-dev teams staring at a blank slot, a flat report, or a bidder that went quiet — and anyone who inherited a wrapper without documentation.
- Best next read
- Prebid & Header Bidding for the full stack guide; Prebid.js 10 → 11 Migration if the symptoms started with an upgrade.
Twelve diagnostics, cited once, used everywhere below.
Every branch in the tree resolves to one of these. All are free, most are one console line, and together they answer the only four questions that matter: what loaded, what bid, what targeted, and what served.
| Diagnostic | What it answers | Source |
|---|---|---|
?pbjs_debug=true | URL parameter: turns on console MESSAGE logs and surfaces JS errors Prebid otherwise swallows. Step zero of every session. | Troubleshooting guide |
pbjs.version; | Which build is actually on the page — and whether pbjs exists at all. | Troubleshooting guide |
pbjs.getConfig() | The effective configuration: timeout, price granularity, consent management. | Troubleshooting guide |
pbjs.adUnits | Registered ad units — bidders, mediaTypes, sizes. Empty means the auction has nothing to auction. | Troubleshooting guide |
pbjs.getBidResponses(); | Every bid received, per ad unit, in the order received — the single most-used troubleshooting call. | Troubleshooting guide |
pbjs.getAdserverTargeting(); | The exact key-values (hb_pb, hb_adid, hb_bidder…) that will be sent to the ad server. | Troubleshooting guide |
pbjs.getAllWinningBids(); | Bids that won their slot — with pbjs.getHighestCpmBids() for the pre-render view. | Troubleshooting guide |
pbjs.onEvent('bidResponse', …) | Per-bidder timing forensics: listeners on auctionInit, bidRequested, bidResponse, noBid, auctionEnd build the timeline that convicts a timeout. | Troubleshooting guide |
googletag.openConsole(); | The GAM Publisher Console: per slot, which line item served and why — the border between Prebid-side and ad-server-side failures. | GAM Help: Publisher Console |
debugging module intercept | Injects fake bids matched by ad unit or bidder — the decisive split between ad-server problems and demand problems. Persists in sessionStorage; deactivate after use. | Debugging module |
Professor Prebid | The official Chrome extension: ad units, bids, timeline, and user IDs as a GUI; its Tools tab toggles the debugging module without code. | Debugging tools |
"test": 1 / storedauctionresponse | Server-side visibility: "test":1 marks requests non-billable and returns debug info; ext.prebid.debug returns debug only; storedauctionresponse serves canned bids to isolate plumbing. | PBS troubleshooting |
The standard opening sequence.
// 0 · reload with ?pbjs_debug=true appended to the page URL
// (MESSAGE logs on, swallowed errors surfaced)
pbjs.version; // which build is actually on the page
pbjs.getConfig(); // effective timeout, priceGranularity, consent
pbjs.adUnits; // registered ad units: bidders, mediaTypes, sizes
pbjs.getBidResponses(); // every bid received, per ad unit, in order
pbjs.getAdserverTargeting(); // the exact key-values that will hit GAM
pbjs.getAllWinningBids(); // which bids won
googletag.openConsole(); // GAM Publisher Console: which line item served, why Nothing happens: no bid requests fire.
The emptiest console is the most structured symptom: only three things sit upstream of every bidder — the library, the ad units, and consent. Check them in that order; each check eliminates the next.
Decisive check
Reload with ?pbjs_debug=true, then run pbjs.version; and pbjs.adUnits. An error, an empty array, or a silent console with both present each names a different branch below.
| If you see | Likely cause | Fix | Source |
|---|---|---|---|
| pbjs.version throws a ReferenceError | Prebid.js never loaded — ad blocker, tag-manager ordering, wrong script path | Confirm the script request in the network tab; load Prebid ahead of deferred tag-manager containers; retest with blocking extensions off. | Troubleshooting guide |
| pbjs.version answers, but pbjs.adUnits is empty | Ad units were never registered — a SPA route rendered before addAdUnits ran, or que ordering put the first requestBids ahead of registration | Register ad units before the first requestBids; re-register on SPA route changes. | Troubleshooting guide |
| Ad units present, no bidRequested events, no adapter traffic | Consent gating: the CMP never resolves, so the auction stalls until the CMP timeout (issue #7156); or the CMP settles late and the first auction leaves with undefined consent (issue #5462) | Raise the consentManagement timeout, guarantee the CMP loads before requestBids, verify with pbjs.getConfig().consentManagement. | TCF consent module |
| Under GDPR, specific bidders or whole ad units silently vanish | Since Prebid 5 there is no allowAuctionWithoutConsent — declined consent plus gdprEnforcement drops bidders or ad units entirely (issue #13023) | Configure enforcement rules deliberately; a bidder dropped by enforcement is policy working, not a bug. | Prebid.js #13023 |
The auction runs, but a bidder returns no bids.
First establish which of two silences you have: pbjs.getBidResponses() with the bidder absent means the request never left; present-with-empty means the endpoint answered with nothing. The network tab settles it in seconds.
Decisive check
The fake-bid intercept below. It splits the search space in half in one move: a rendered fake bid proves ad units, targeting, line items, and creatives all work — leaving only the bidder relationship. No other single test eliminates as much.
// The decisive test: prove the pipeline with a fake bid.
// If this renders end-to-end, the plumbing works — the gap is demand.
pbjs.setConfig({
debugging: {
enabled: true,
intercept: [{
when: { adUnitCode: 'test-div', bidder: 'bidderA' },
then: { cpm: 10.00 }
}]
}
});
// The module persists in sessionStorage across page loads —
// deactivate when done:
pbjs.setConfig({ debugging: { enabled: false } }); | If you see | Likely cause | Fix | Source |
|---|---|---|---|
| Bidder listed in getBidResponses() with an empty bid set; its request visible in the network tab | Wrong adapter params — placementId / siteId / zone typos. Adapters fail silently on bad params; this is the largest failure class in the GitHub issue history | Validate every parameter name and type against the bidder’s page in the official reference; copy IDs from the SSP UI, never retype. | Bidder reference |
| No request leaves the browser for that bidder at all | The bidder is not in the ad unit’s bids array for that mediaType, or consent enforcement dropped it | Inspect pbjs.adUnits for the unit; check enforcement rules (symptom 1, branch four). | Troubleshooting guide |
| Request fires, endpoint returns 200, still no bid | mediaTypes or size mismatch — the seat is not approved for the size, or the bidder does not support the declared mediaType | Compare mediaTypes and sizes against what the SSP account is approved to serve; confirm size approval with the SSP. | Common issues |
| Bids arrive, then vanish before targeting | Floors module filtering — the bid came in under the configured floor | Log the floors config from getConfig(); test with floors disabled in staging. | Price Floors module |
| Everything checks out and there is still no bid | Legitimately no demand for this user, geo, or inventory | Prove it with the fake-bid intercept below: if the fake bid renders, stop debugging config and take the demand question to the SSP. | Debugging module |
Bids exist, but never reach the ad server.
pbjs.getBidResponses() has bids; pbjs.getAdserverTargeting() is empty. The bids are real and worthless — they missed the ad call. This is a race, an ordering bug, or a key-control ceiling; the event timeline tells you which.
Decisive check
Listeners on auctionInit, bidRequested, bidResponse, and auctionEnd give a per-bidder timeline. Bids timestamped after the ad server request left convict the timeout; bids in time convict the targeting call or the key limits.
// docs.prebid.org/dev-docs/faq.html — starting points, not gospel:
// auction timeout ≤ 1,000 ms · page failsafe ≤ 3,000 ms ·
// failsafe STRICTLY greater than the auction timeout.
pbjs.setConfig({
bidderTimeout: 1000 // pbjs adds timeoutBuffer (default 400 ms) on top
}); | If you see | Likely cause | Fix | Source |
|---|---|---|---|
| getBidResponses() fills up — but only after the ad has rendered; targeting empty at the ad call | Timeout starvation: bids land after the ad server call left. bidderTimeout is not the whole story — an internal timeoutBuffer (default 400 ms) extends it (issue #3368), and a busy main thread mis-times bidders (issue #1046) | Build a per-bidder timeline with event listeners; extend the auction timeout only with impression-loss measurement attached; move chronically slow bidders server-side. | Prebid.js FAQ |
| Bids in time, getAdserverTargeting() still empty at the ad call | setTargetingForGPTAsync() never called — or called before bids returned | Call it inside bidsBackHandler (or on auctionEnd), before the googletag display/refresh that fires the ad request. | Troubleshooting guide |
| Some ad units carry full targeting; others silently lose keys | Key-string controls: targetingControls.auctionKeyMaxChars trims the key set, and sendBidsControl.bidLimit caps bids in sendAllBids mode | Audit both controls in getConfig(); raise the limits deliberately or reduce the keys you send. | Ad ops key-values |
Targeting reaches GAM, but Prebid line items don’t win.
From here the failure is inside Ad Manager, and the Prebid console can only watch. The most common cause is arithmetic, not configuration: a bid quantized into a bucket no line item targets.
Decisive check
googletag.openConsole(); — per slot, which line item served and why. For history, GAM’s Delivery Tools (Delivery Inspector and each line item’s “Check delivery”) answer the same question over past traffic (Google Ad Manager Help).
| If you see | Likely cause | Fix | Source |
|---|---|---|---|
| hb_pb arrives (say 0.71) but no Prebid line item serves | Price-granularity mismatch: site configured at $0.10 buckets, line items built at $0.50 — $0.71 matches nothing | Align priceGranularity in pbjs.getConfig() exactly with the line-item rate card; rebuild whichever side is wrong. | Common issues |
| hb_pb shows 0.00 | The bid fell below the first price bucket and rounded down; no line item targets $0.00 | Lower the first bucket, or accept that sub-bucket bids do not serve. | Common issues |
| Prebid line item eligible, but a sponsorship or house item serves | Higher-priority line-item types outrank Prebid’s price-priority line items | Read the winning item’s priority in the Publisher Console; adjust priorities or accept the direct sale. | Common issues |
| hb_ keys missing or mangled in GAM | Key-values never created under Inventory → Key-values — or GAM’s 20-character key limit truncates hb_pb_BIDDERNAME in sendAllBids mode | Create the keys; keep bidder codes short enough that hb_pb_<biddercode> survives 20 characters. | GAM step by step |
| Specific price buckets never serve | The 450-line-items-per-order limit was hit mid-setup, leaving gaps in bucket coverage | Audit line-item coverage against the granularity; split buckets across orders. | GAM step by step |
The line item serves, but the slot is blank.
Won-not-rendered: the Publisher Console shows the Prebid line item delivered, and the user sees nothing. Everything upstream worked; the creative — the one GAM asset Prebid cannot inspect — did not.
Decisive check
Console for renderAd messages and AD_RENDER_FAILED events, next to the Publisher Console’s delivery confirmation. Delivered-plus-blank-plus-silent points at the creative body; a render error names its own branch.
| If you see | Likely cause | Fix | Source |
|---|---|---|---|
| Line item delivers, slot stays blank, no render error anywhere | The GAM creative does not carry the Prebid Universal Creative or the hb_adid macro — nothing tells the page which cached bid to paint | Rebuild the creative from the documented PUC template. | Prebid Universal Creative |
| Blank only in SafeFrame slots; renderAd loops or silent failures | The creative is the friendly-iframe variant; SafeFrame needs the SafeFrame-specific setup — classic GitHub classes: renderAd loops without finding the bid (#1426, #1698), SafeFrame render failure not even reported (#7702) | Serve the SafeFrame creative variant wherever GAM serves SafeFrame; standardize the SafeFrame setting per line item. | Prebid Universal Creative |
| First slot renders; later slots on the same page starve | GAM serves one creative per line item per page — multi-slot pages need duplicated creatives on every Prebid line item | Attach multiple duplicate creatives per line item — the documented setup step most often skipped. | GAM step by step |
| Video: winning bid, no ad in the player | hb_uuid / hb_cache_id cache miss — the VAST was never cached, or the player forwards the wrong cache URL | Verify the Prebid Cache configuration and the video key-values the player passes through. | Ad ops key-values |
The silent killer on multi-slot pages
GAM serves one creative per line item per page. A page with five ad slots and one creative per Prebid line item renders the first win and starves the other four — no error, no log, just blanks. Duplicating creatives on every line item is in the official setup guide and skipped constantly.
Everything renders — the numbers don’t match.
Three systems count the same auction: Prebid’s bidWon events, GAM’s line-item impressions, the SSP’s dashboard. They will never agree exactly; the work is separating structural deltas from configuration errors.
Decisive check
Compare all three counts over one identical window. A constant-ratio gap points at gross-vs-net or currency; a same-direction few-percent drift points at late winners and window misalignment; chaos points at the macro branch.
| If you see | Likely cause | Fix | Source |
|---|---|---|---|
| SSP dashboard consistently above GAM revenue | Gross bids compared against net reporting | Normalize with bidCpmAdjustment in bidderSettings; compare like for like before escalating. | Prebid.js FAQ |
| Price paid differs from price recorded | The AUCTION_PRICE macro resolved client-side causes price reporting drift | Reconcile against the SSP’s own auction records; treat client-resolved macros as approximate. | Prebid.js FAQ |
| Impression counts drift a few percent, always the same direction | Late-arriving winners the SSP counted but GAM never served; measurement windows misaligned | Align report windows and time zones; baseline the structural delta instead of chasing it monthly. | Prebid.js FAQ |
| Multi-currency bidders wildly over- or under-reported | Currency module absent — foreign-currency bids compared unconverted | Add the currency module with an explicit adServerCurrency. | Currency module |
Prebid Server underperforms the same bidders client-side.
Moving bidders server-side trades browser identity for latency — and the identity loss is usually the whole story. This symptom runs parallel to the pipeline: everything above can pass while the s2s slice quietly underdelivers.
Decisive check
Turn on server-side debug output, then serve a canned bid via storedauctionresponse. If the canned bid flows end-to-end, the plumbing works and the gap is identity or consent — not configuration. PBS-Java operators can additionally capture live exchange traffic with the /logging/httpinteraction admin endpoint.
// Client-side: mark s2s requests as test traffic
// ("test":1 = non-billable + debug info in the response)
pbjs.setConfig({ ortb2: { test: 1 } });
// Raw PBS request alternatives:
// "test": 1 → non-billable + debug
// "ext": { "prebid": { "debug": true } } → debug only, still billable
// AMP endpoints: append &debug=1
// Isolate plumbing from demand with a canned response:
// imp[].ext.prebid.storedauctionresponse = { "id": "..." } | If you see | Likely cause | Fix | Source |
|---|---|---|---|
| Server-side bidders bid rarely; responses carry sync URLs instead of bids | Cookie-sync deficit: with no synced ID on the PBS domain, many exchanges return sync URLs instead of bids and bid conservatively on unsynced users (foundational issue prebid-server#41). The mechanism is documented; match-rate magnitude figures are vendor-sourced or not publicly documented | Run /cookie_sync warm-up early in the session; verify the sync pixels fire; compare per-bidder match rates over time. | PBS cookie sync |
| s2s bidders time out client-side while PBS logs show in-time responses | The inner s2s timeout is misaligned with the outer client timeout, so PBS answers after the wrapper gave up (issue #12287) | Set the inner s2s timeout to roughly half the outer client timeout so PBS returns before the client deadline. | Prebid.js #12287 |
| A bidder passes client-side but vanishes server-side under GDPR | TCF enforcement removes server-side bidders that pass client-side (issue #12084) | Audit the PBS GDPR configuration and vendor IDs separately from the client-side consent config. | Prebid.js #12084 |
| PBS errors on specific imps; response mapping throws | Stored request / stored imp misconfiguration (issue #7027) | Validate stored request IDs; serve a storedauctionresponse to prove the plumbing, then reintroduce live demand. | PBS troubleshooting |
What this page does not cover.
A decision tree earns trust by declaring its edges. These are outside this one.
- Adapter-by-adapter parameter semantics — the official bidder reference (docs.prebid.org/dev-docs/bidders.html) is the only current source; params change without notice.
- Prebid.js 10 → 11 upgrade regressions — covered by this site’s dedicated migration page; if symptoms began with an upgrade, start there.
- Ad servers other than Google Ad Manager — the ad-server-side branches here (Publisher Console, key-values, line-item priority) are GAM-specific.
- Managed-wrapper dashboards — vendors operating Prebid on your behalf expose their own diagnostics; the console commands still work underneath.
- Prebid Mobile SDK and AMP beyond the debug flag — separate render paths with their own failure modes.
Frequently asked questions.
The questions operators actually search, answered in this page’s own words. Each is covered in depth in its symptom section above.
Why is Prebid returning no bids?
Split the question with pbjs.getBidResponses(). If the bidder never appears, the request never left — check ad unit registration and consent gating. If it appears with an empty bid set, the causes in order of likelihood: adapter parameter typos (validate against the official bidder reference — adapters fail silently on bad params), mediaTypes or sizes the seat is not approved for, floors filtering, and genuinely no demand. The decisive test is the debugging module’s fake-bid intercept: if an injected bid renders end-to-end, the configuration works and the gap is demand.
Why are Prebid bids not showing up in Google Ad Manager?
Two different failures. If pbjs.getAdserverTargeting() is empty while getBidResponses() has bids, the bids never reached the ad call — timeout starvation, setTargetingForGPTAsync ordering, or key-string limits. If targeting arrives but no Prebid line item serves, the failure is GAM-side: price-granularity mismatch between the site config and the line items, hb_pb rounding to 0.00 below the first bucket, higher-priority line items, or hb_ key-values never created under Inventory. googletag.openConsole() tells you which world you are in.
What is the best Prebid timeout value?
There is no universal best value. The official Prebid.js FAQ’s starting points: auction timeout at or under 1,000 ms, page-level failsafe at or under 3,000 ms, and the failsafe strictly greater than the auction timeout — equal values build a timeout trap. Two mechanics matter when tuning: Prebid adds an internal timeoutBuffer (default 400 ms) on top of bidderTimeout, and a busy main thread can mis-time bidders. Change timeouts only with a per-bidder event timeline and impression-loss measurement attached.
Why does the winning Prebid bid not render?
The line item served but the creative has nothing to paint. The usual causes: the GAM creative lacks the Prebid Universal Creative or hb_adid macro; the slot is SafeFrame and the creative is the friendly-iframe variant; the page has multiple ad slots and GAM’s one-creative-per-line-item-per-page rule starves the rest — duplicate creatives on every Prebid line item; and for video, an hb_uuid cache miss between Prebid Cache and the player.
Why does Prebid Server return fewer bids than the same bidders client-side?
Usually identity, not configuration. With no synced ID on the Prebid Server domain, many exchanges return cookie-sync URLs instead of bids and bid conservatively on unsynced users — the mechanism is documented in the PBS cookie-sync docs; magnitude figures are vendor-sourced or not publicly documented. Then check the inner s2s timeout against the outer client timeout, and TCF enforcement dropping server-side bidders that pass client-side. storedauctionresponse isolates plumbing from demand with a canned bid.
Debugging a wrapper with revenue attached?
A decision tree finds the branch; production wrappers need the branch fixed without dropping the slots that still work — timeout posture, key-value hygiene, server-side migration, and monitoring that catches the silent failures first.