Ad Ops Dashboard Guide
Internal playbook for operating the Bidding Player platform — how to use the dashboard, how to onboard new publishers, and how to extend the system with new SSPs.
What this is
Bidding Player is a one-script video header-bidding solution we ship to publishers. Each publisher pastes our <script> tag into their page; the engine runs a Prebid auction, stitches the winning bid into their GAM master ad tag, and plays the resulting VAST through video.js + IMA.
The ad ops team owns three things on this platform:
- Tag generation — using the dashboard to produce a publisher-specific
<script>snippet. - SSP onboarding — triggering a new Prebid bundle build when a publisher needs a new bidder.
- Operations — testing tags before handoff, debugging publisher-reported issues, and managing version rollouts.
Architecture & pieces
Three artifacts ship to each publisher. All three are hosted on jsDelivr at versioned, immutable URLs:
| Artifact | What it does | Per-publisher? |
|---|---|---|
engine/player.js |
The shared player engine. Reads data-* attributes, registers a video ad unit, runs the auction, stitches hb_* targeting into the GAM tag, plays the creative via IMA. |
One file |
prebid/<bundle>.js |
The Prebid.js library, custom-built with only the bidder adapters a specific publisher needs (smaller payload, faster load). | Catalog |
<script> snippet |
The HTML tag pasted into the publisher's page. Carries the publisher's bidder params, GAM tag URL, content URL, player config. | Unique |
One engine + N bundles + M snippets. The engine never changes per publisher. The bundle is shared by all publishers using the same set of SSPs. The snippet is unique per placement.
Sandbox testing
The Sandbox Testing Suite tab has two modes, set by the Bid Source selector in Auction Configuration:
- Simulated (default) — inject a mock CPM and verify the full GAM stitch + IMA playback path end-to-end. Does not call real bidders.
- Real bid request — fires a live
pbjs.requestBids()against the ticked SSPs and reports each bidder's real status (bid / no-bid / timeout / error), CPM, and response time. Use it to QA adapter wiring + params and measure SSP latency. Caveat: requests come from the dashboard's domain, which is usually not allowlisted, so no-bid is expected here — run from an allowlisted publisher page to see live CPMs. The timings, adapter firing, and errors are real regardless.
When to use it
- Before handing a tag to a publisher — verify the GAM tag responds and IMA plays the creative.
- When debugging "ad not playing" reports — the structured phase logs (Phases 1–9) show exactly where the chain breaks.
- To check that a new Prebid bundle initializes and registers without errors.
How to read the log
The log is divided into 9 numbered phases. Skim phase headings first; expand only the phase that errored. Phase 6 ("Appending Prebid response into GAM master ad tag") is where the stitched URL appears — confirm hb_pb, hb_bidder, and hb_uuid all show up in cust_params.
Tag generator
The Production Tag Generator tab produces the deployable <script> snippet for a publisher. The flow:
Fill the GAM master ad tag
Paste the publisher's GAM master tag URL. It must end with
correlator=so the engine can append a unique correlator per impression. Ifcust_paramsis missing, the engine adds it.Choose the placement
Set Placement to
instream(ad plays against a content video — needs a Video Content URL) oroutstream(in-article, no content video; the slot autoplays muted on scroll-into-view and collapses when done). Outstream dims the Video Content URL field automatically.Set pricing controls (optional)
Strategic Bias (off by default), Floor Min, and Floor Max are toggles in Ad Serving — flip them on only when the publisher needs them. For instream, the Sticky / Floating Player toggle (in Player Behaviour) makes the player float to the corner on scroll-out.
Tick the bidders
In the Bidders card, tick every SSP that should compete in this auction. Limelight is on by default. For each ticked bidder, fill in its required params (e.g. publisher ID, placement ID).
Verify the Prebid URL matches
The engine + Prebid URLs are pinned to the latest release automatically (the version selector was removed in v2.3.0). They live in the collapsible Hosting & CDN panel — override only when testing a feature branch. The Prebid URL must point at a bundle that contains every adapter you ticked — see Prebid bundle catalog.
Generate & copy
Click Generate Production Tag. Validation runs first — required fields and bidder params get red borders if invalid. Once green, copy the snippet from the output box and hand it to the publisher.
Numeric bidder params (Ad Unit ID, Placement ID, Account ID, etc.) accept text but must parse as numbers. If a publisher gives you a value like "972556929-A", strip the suffix or push back — Prebid will reject the request.
Field reference
| Field | Purpose | Required |
|---|---|---|
| GAM Master Ad Tag URL | Publisher's GAM tag, must end with correlator=. | Yes |
| Placement | instream (ad against content video) or outstream (in-article, no content video; autoplays muted on scroll-into-view, collapses when done). Emits data-placement. | Yes |
| Video Content URL | The video the publisher wants in the player; pre-roll plays before it. Instream only — dimmed for outstream. | Instream |
| Bidder Timeout | How long Prebid waits for bid responses before calling the auction. Default 1200 ms. | No |
| Strategic Bias | Constant added to the winning bid's hb_pb before stitching, to tip ties to header bidding. Toggle — off by default; off emits data-bias="0.00". | No |
| Floor Min / Floor Max | Toggles. Min rejects sub-floor bids (fall through to house); Max caps the winning CPM before bias + bucketing. Emit data-floor-min / data-floor-max when on. | No |
| Prebid.js URL | The bundle file in prebid/ that contains the adapters you ticked. Pinned to the latest release; in the Hosting & CDN panel. | Yes |
| Bidder picker | One card per SSP. Tick to include; fill params. Generates the data-bidders JSON. | ≥1 |
| Sticky / Floating Player | Instream only. Floats the player to the corner on scroll-out (keeps playing, × to close). Emits data-sticky="true". Off by default. | No |
| Loop Content | Replays the content video after it ends. On by default — keeps inventory available for refresh. | No |
| VPAID Access Mode | insecure = highest yield (allows insecure VPAIDs). Switch to enabled only if the publisher requests it. | No |
Prebid bundle catalog
Prebid.js is modular — every bidder is an adapter you opt into at build time. Shipping every adapter would bloat the file (3 MB+ for the full Prebid catalog). Instead, we maintain a catalog of pre-built bundles, each containing just the adapters a particular set of publishers needs.
Catalog layout
Bundles live under prebid/ in the repo. Naming convention for custom bundles: prebid-<bidder1>-<bidder2>.js. The current catalog:
| File | Adapters | Size | Used by |
|---|---|---|---|
prebid/prebid.jsDefault |
limelightDigital, appnexus, rubicon (Magnite), pubmatic, openx, incrementx | 286 KB | Every publisher — covers the 6 common SSPs out of the box |
Every bundle automatically includes the consent modules (consentManagementTcf, consentManagementUsp, consentManagementGpp, tcfControl) — the engine's default Prebid config depends on them.
The default prebid.js now ships with all 5 SSPs (Limelight + AppNexus + Magnite + PubMatic + OpenX). No more "build a new bundle for each publisher" workflow for the common case. Only build a custom bundle when a publisher needs an SSP outside this set.
Triggering a new bundle build
When a publisher needs an SSP we don't have a bundle for yet, build a new one. This is a GitHub Actions workflow — no local tooling needed.
Open the Actions tab
Go to github.com/shashwatsilverpush/bidding-player/actions. Select Build Prebid bundle from the workflow list on the left.
Click "Run workflow"
Top-right of the runs list. A form drops down with three inputs.
Fill the inputs
modules — comma-separated Prebid adapter names. Find names at docs.prebid.org/dev-docs/bidders.html. Example:
limelightDigitalBidAdapter,appnexusBidAdapter.bundle_name — output filename without
.js. Follow the convention:prebid-limelight-appnexus.prebid_version — Prebid.js release tag, e.g.
v11.11.0. Stick to the same version we've been building against unless there's a reason to upgrade.Run & wait
~3 minutes. The workflow clones Prebid.js, runs
gulp build --modules=…, commits the result toprebid/<bundle_name>.json main.Tag a release
Once the commit lands, bump
VERSIONand tag the repo:git tag v2.X.Y && git push --tags. jsDelivr will serve the new bundle at@v2.X.Ywithin a few minutes.Use it in the tag generator
In the dashboard, change the Prebid URL filename (in the Hosting & CDN panel) to your new bundle, tick the new bidder, fill its params, generate the tag. The engine/Prebid URLs are pinned to the latest release automatically.
Open the failed run, expand "Build bundle" step. The most common error is a typo in the module name — Prebid will list the unknown module. Fix and re-run.
Adding a bidder to the picker UI
The bidder picker in the dashboard reads from a single JS array in index.html: BIDDER_CATALOG. To make a new SSP selectable in the UI, add an entry there.
// index.html, inside adopsModule()
const BIDDER_CATALOG = [
{ id: 'limelightDigital', label: 'Limelight Digital', module: 'limelightDigitalBidAdapter', defaultOn: true, params: [...] },
{ id: 'appnexus', label: 'AppNexus / Xandr', module: 'appnexusBidAdapter', params: [
{ key: 'placementId', label: 'Placement ID', required: true, type: 'number' }
]},
// ← add a new bidder here
];
Fields per entry:
id— the Prebid bidder code (used as the"bidder"key in the request).label— what shows in the UI.module— the adapter name (only used as a hint label in the UI; the actual module is included via the Prebid bundle).defaultOn— iftrue, the checkbox is ticked on first render.params— array of param schemas:key,label,required,type('number'or omit for string),default,hidden(auto-injected without a UI input).
Adding here does not automatically include the adapter in any bundle — the bundle build must include the matching module name. Order is: build bundle → add to catalog → use in tag.
Publisher onboarding playbook
What to ask the publisher
- Their GAM master ad tag URL (must end with
correlator=). - Which SSPs they want competing (and the bidder-specific IDs — publisher ID, placement ID, etc.).
- The video content URL.
- Their domain — required to allowlist with the SSPs.
Pre-launch checklist
- Domain is allowlisted at every SSP they're using. #1 cause of zero fill.
- GAM line items targeting
hb_pbare set up at $0.10 increments. - VAST wrapper creative uses
%%PATTERN:hb_uuid%%→prebid.adnxs.com/pbc/v1/cache. - Tag tested locally with
?debug=true— auction runs, GAM responds, IMA plays creative.
Handoff
Send the publisher: the generated <script> snippet, a link to the publisher integration guide, and the PDF version for offline reference. Their docs do not reference this ad ops console — keep internal flow internal.
Release & deploy cycle
- Land changes on
main. BumpVERSIONfile (the CI version-drift check enforces consistency betweenVERSIONand embedded jsDelivr URLs). - Tag the release:
git tag v2.X.Y && git push --tags. - Update the dashboard's default version field if appropriate (in
index.html, theadopsVersioninput). - Promote to the auto-update channel: set
stableinengine/channel.jsonto the new version, commit, then purgehttps://purge.jsdelivr.net/gh/shashwatsilverpush/bidding-player@main/engine/channel.json(and@2/engine/loader.js) so auto-update tags pick it up within minutes. - Rollback (if a release misbehaves): set
channel.jsonstableback to the previous version, commit, purge — every auto-update publisher reverts. Canary: setcanary+canaryPct(0–100) to roll a release to a sticky % of viewers first. - Pinned publishers on older tags are unaffected — jsDelivr serves immutable bytes per tag.
When publisher-guide.html changes on main, the pdf-regen.yml workflow regenerates publisher-guide.pdf via Chrome headless. No manual step needed.
Common issues
Publisher reports "no ads playing"
- Open their page with
?debug=true. Check the engine log. - Phase 3 (auction) — did Prebid issue a bid request? If not, the bundle is missing the adapter.
- Phase 4 (response) — empty
seatbid? Domain allowlisting issue at the SSP. - Phase 6 (stitch) — is
hb_uuidin the GAM URL? If missing, engine version is < v1.2.0; ask publisher to update. - Phase 8 (IMA) — VAST loads but doesn't play? Usually VPAID mode mismatch or mixed content.
"Auction canceled" in the log
Consent module canceled it. Check the consent config — we ship with static TCF (gdprApplies: false) and dropped GPP defaults in v1.2.1/1.2.2 specifically to prevent this. If the publisher has a real CMP, switch the engine config from static to iab.
Bidder silently dropped
Prebid logs a warning like "video ad unit containing bidders that don't support video: X". The bundle they're loading doesn't include the adapter. Either rebuild the bundle including the missing adapter, or remove that bidder from the snippet.
Tag generator validation never passes
Open browser DevTools → Console. Look for red errors. The most common cause is a paste of a GAM URL with smart quotes from a Google Doc — re-paste from a plain-text source.
Changelog
-
DashboardSandbox real bid request mode. A "Bid Source" selector in the Sandbox: Simulated (mock CPM, default) or Real bid request — fires a live
pbjs.requestBids()and shows each SSP's real status, CPM, and response time. Dashboard-only (no engine change). Expect no-bid from the dashboard origin; run from an allowlisted page for live CPMs. -
v2.5.0Auto-updating engine tag. New "Engine Delivery" choice in the tag generator: Auto-update (default) emits a tiny loader that loads the latest stable engine from a release channel (
engine/channel.json), so publishers never re-issue the tag when we ship an update; Pinned keeps the old frozen@vX.Y.Zbehavior. Rollback = setchannel.jsonstableback + purge; canary =canary+canaryPct. -
v2.4.3Outstream unmute control. Outstream ads autoplay muted, so the player now shows a tap-for-sound mute/unmute toggle (bottom-left) while the ad plays. IMA's own controls (ad label, countdown, clickthrough, skip) are unchanged — the toggle is an overlay for the one control IMA doesn't provide generically.
-
v2.4.2Outstream muted-autoplay fix. The outstream renderer never actually muted the ad, so the browser blocked its autoplay (outstream starts on scroll, with no user click) — the slot would expand to a black frame for a split second and then collapse. The engine now mutes the outstream ad (video element +
adsManager.setVolume(0)) so muted autoplay is allowed, and logs IMA ad errors under?debug=true. -
v2.4.1Outstream loop fix.
data-loopis a content-video setting and is now ignored for outstream in the engine, and omitted from generated outstream tags by the dashboard. Fixes a report where an outstream tag carryingdata-loop="true"made the ad flash for a split second and then disappear (the loop attribute restarted the ad creative in the shared video element, colliding with theALL_ADS_COMPLETEDcollapse). -
v2.4.0Sticky / floating instream player. New
data-sticky="true"attribute (and a "Sticky / Floating Player" toggle in Player Behaviour, off by default). When an instream player scrolls out of view it floats to the bottom-right corner and keeps playing — with a × to dismiss — then snaps back inline on scroll-up. The original layout space is preserved and the live IMA ad creative is resized to fit the floating frame. Instream only (outstream already auto-collapses). Lifts viewability + completed-view rates. -
v2.3.0Outstream video support. New
data-placement="outstream"and a Placement dropdown in the tag generator. Outstream needs no content video: the slot stays collapsed until it scrolls ≥50% into view, autoplays muted, then collapses on completion/error. All engine render paths now route through arender()dispatcher by placement. Also: the dashboard's Release Version selector was removed — engine/Prebid URLs are pinned to the latest release (override in Hosting & CDN). -
v2.2.0Floor price controls + GAM key-value reference. New
data-floor-min(reject sub-floor bids → house line item) anddata-floor-max(cap the winning CPM before bias + bucketing), wired into the engine, tag generator (toggles), and sandbox. Added a GAM Key-Value reference tab documenting standard + per-bidderhb_*keys, the%%PATTERN:hb_uuid%%macro, and line-item setup. -
v2.1.3Default bundle expansion + Pages deploy fix. The default
prebid/prebid.jsnow contains all 5 common SSPs (Limelight + AppNexus + Magnite + PubMatic + OpenX) built from Prebid.js 11.13.0 — eliminates the "build a new bundle per publisher" step for the common case (286 KB total, +37 KB over the v2.0.0 Limelight-only build). Migrated GitHub Pages deploy from legacy Jekyll builder toactions/deploy-pagesworkflow after the legacy builder hung twice on large content commits. Fixed a latent bug inbuild-prebid-bundle.yml— Prebid.js tags lack thevprefix (e.g.11.13.0, notv11.13.0). Added.nojekyll. -
v2.0.0Multi-bidder support. New
data-biddersJSON attribute lets a single tag run an auction across multiple SSPs. Per-publisher Prebid bundle catalog underprebid/.workflow_dispatchCI for on-demand bundle builds. Dashboard now has a checkbox-based bidder picker (replaces v1's JSON textarea). Legacydata-pub-id/data-adunit-id/data-hostattrs continue to work for v1 tags in the wild. -
v1.2.2TCF static config. Switched to
cmpApi: 'static'withgdprApplies: falsesotcfControlstops canceling auctions when no CMP is present on the publisher page. -
v1.2.1Dropped GPP from defaults. The GPP consent module was canceling auctions on every publisher without a GPP-aware CMP. Pulled it from the default config.
-
v1.2.0Full
hb_*targeting. Engine now stitches the complete targeting set (hb_uuid,hb_size,hb_format,hb_adid) into GAM'scust_params, not justhb_pb/hb_bidder. Withouthb_uuid, GAM couldn't resolve cached VAST creatives. Also: dynamic player size (readsgetBoundingClientRectat auction time); consent management modules wired in. -
v1.1.0Custom Limelight Prebid build. The stock Prebid bundle was silently dropping the Limelight adapter ("video ad unit containing bidders that don't support video: limelightDigital"). Swapped in a build with the supplier-provided adapter.
-
v1.0.xInitial release. Versioned jsDelivr hosting, CI version-drift guard, sandbox testing tab, production tag generator, structured phased logging, Claude high-contrast dark theme, publisher PDF guide with auto-regen workflow.