Skip to content
Sanyam Jain
Back to home

2024 – 2025 · Senior Engineer · Ads / Frontend

Rebuilding an ads delivery SDK around Prebid

Owned the rebuild of a multi-surface ads delivery layer — modular partner adapters, Prebid header bidding, consent integration, and the instrumentation taxonomy that ties revenue movement to specific code changes.

4 min read·
  • Ads
  • Prebid
  • Header bidding
  • Revenue
  • Performance

The brief

A consumer publisher running display ads across multiple brands and surfaces — desktop, mobile web, AMP, in-app webview — through a shared delivery SDK. The previous shape was a monolithic delivery script that had accumulated years of partner integrations, viewability vendors, and consent hooks. Revenue was leaking from places nobody could attribute: the auction timeout was too short for some surfaces, new partners took weeks to onboard, and three teams had three different numbers for the same metric.

I owned the rebuild of the delivery layer — modular adapters, Prebid header bidding done deliberately, and the instrumentation taxonomy that lets revenue movement actually point at a code change.

How publishers make money — quickly

Every ad slot on the page goes through an auction. Header bidding (via Prebid.js) runs that auction on the client, in parallel, across multiple demand sources (SSPs) — each one bids on the impression. The top bid is passed to the ad server (typically Google Ad Manager) as a key-value targeting parameter. The ad server runs its own final auction that includes the header-bid price, direct-sold campaigns, and the open exchange. The highest CPM wins and gets rendered.

The whole thing happens client-side, in a few hundred milliseconds, while the page is loading. The interesting engineering — and the interesting revenue lever — is in how you tune that window.

Page loadslots definedPREBID AUCTIONparallel · 1500 ms timeoutSSP ASSP BSSP CSSP D…N more adapters, gated by consentTop bid selectedAd server (GAM)final auctiondirect · exchange · AdXCreativerendered in slotCLIENTCLIENTSERVERCLIENT
A header-bidding auction runs in parallel on the page before the ad server is called. Winning bid becomes a key-value targeting param; the ad server runs its own auction on top.

What I shipped

Modular delivery pipeline. Four concerns with explicit contracts, each independently testable:

  • Configuration resolution. Page context → slot decisions. Pure, no I/O, exhaustively unit-tested.
  • Auction orchestration. Prebid bootstrap, parallel SSP calls, timeout race, consent gating.
  • Partner adapters. One module per SSP, minimal contract surface, explicit lifecycle hooks. Adding a new partner is one file.
  • Ad-server handoff. Winning bid → GAM key-value targeting, with a taxonomy I could read in the dashboards later.

Prebid integration done deliberately. Configured per-slot bidder lists based on slot type and surface. Tuned the auction timeout per surface — longer on desktop where the network budget allows, tighter on mobile web where every 100ms of LCP shows up in the field data. Wired price-floor logic that varies by inventory tier.

Consent at the orchestration layer. TCF v2 / GPP compliance plugged in once, not in every adapter. Bids only fire for users with the right consent string; GDPR/CCPA gating handled before any partner sees a request.

Instrumentation taxonomy. A single event vocabulary that every layer emits into: slot.defined, bid.requested, bid.received, bid.won, ad.rendered, ad.viewable. Routed to one warehouse so revenue, product, and engineering all read the same numbers from the same funnel.

Lazy-loaded slots. Intersection-observer-based deferral so below-the-fold ad calls don’t compete with critical content for network or CPU. Tuned per surface — what counts as “below the fold” on a mobile article isn’t what it means on a desktop home page.

58%

header bidding

  • Prebid (header bidding)58%
  • GAM direct sold24%
  • AdX exchange14%
  • House / promo4%
Revenue mix after the rebuild. Header bidding became the dominant demand path because the integration surface was no longer the bottleneck on adding partners.

The auction-timeout tradeoff

The single highest-leverage tuning decision in a Prebid setup is the auction timeout. Too short and you miss bids from slow SSPs — revenue falls. Too long and the ad call blocks the page — LCP regresses, users leave, the revenue you captured doesn’t matter.

This is the chart that turned a six-month debate into a one-meeting decision:

Auction timeout vs. captured revenue vs. LCP cost. The sweet spot is where the revenue curve flattens and the LCP curve hasn’t taken off — we landed at 1500 ms.

We landed at 1500 ms — the knee of the revenue curve, before the LCP curve takes off. The point isn’t the number; the point is that the team was tuning against data with a known cost on the perf side, instead of guessing.

Stack

Stack

  • Header biddingPrebid.jsopen source, per-slot configured
  • Ad serverGoogle Ad Manager (GAM360)final auction + reporting
  • IdentityUID2 · ID5 · SharedIDpost-third-party-cookie identifiers
  • ConsentTCF v2 · GPPgated at orchestration layer
  • TelemetryPrebid Analytics + custom warehousesingle event taxonomy
  • FrontendTypeScript SDK · framework-agnosticembeddable on any surface
Vendor-agnostic boundaries. Every layer is swappable behind its contract.

What this unlocked

  • New partner integrations went from multi-week, multi-team efforts to a single adapter module behind a well-defined contract.
  • Auction-timeout tuning became a data-driven loop tied to real bid-return-rate and LCP impact — not folklore.
  • Revenue attribution: changes to floors, timeouts, or bidder lists could be measured against a baseline in days, not quarters.
  • Faster monetised templates — lazy loading and the new orchestration shape both contributed to LCP wins on landing surfaces.

Lessons I keep coming back to

Prebid is config. The biggest revenue lever isn’t adding bidders — it’s tuning the ones you have.

  • Tune timeouts against perf, not folklore. Every extra 250 ms of auction window has a measurable LCP cost. If you can’t see both curves at once, you’re flying blind.
  • Consent belongs at the orchestration layer. Push it into every adapter and you’ll fight the same compliance bug forever.
  • Lazy load aggressively, but instrument carefully. A slot deferred too long is unfilled inventory. A slot rendered too early is wasted bandwidth and a CWV hit.
  • One taxonomy, one warehouse, one number. If revenue, product, and engineering are reading different dashboards, the conversation about “what changed” will never get past arguing about the numbers.
  • Identity is changing under your feet. The third-party-cookie deprecation didn’t kill header bidding — it raised the bar on identity layers. Build the SDK so identifiers are swappable.