Blog

Bid Request Enrichment: How to Make the Same Impression Worth More

Buyers price what they can see. A guide to enriching outgoing bid requests — geo, identity, placement ID, context — without inventing signal you do not have.

19 min read By the Floxis engineering team

One bid request shown twice: the version that left the publisher with most fields blank, and the version that reached the buyer with geography, identity, placement ID and content filled in

Here is a bid request as it left a publisher’s page this morning. It has an impression, a size, a domain, an IP address, and almost nothing else. No city. No user ID of any kind. No placement identifier — just an ad unit code that means something inside that publisher’s CMS and nothing anywhere else. No content categories, no language, no privacy policy flag.

Now here is the same request, for the same impression, arriving at a DSP a few milliseconds later, after passing through an exchange that filled in what it could: country, region, city and time zone resolved from the IP; two extended IDs the exchange had seen for this user before; a stable placement identifier synthesized from the supply partner, the property and the format; the site’s name, content categories and language pulled from what the exchange already knew about that property.

Nothing about the inventory changed. The page is the same page, the user is the same user, the slot is the same slot. What changed is how much of it the buyer can see — and buyers price what they can see.

That is the whole argument: enrichment does not improve your traffic, it makes your traffic legible. A blank field is not neutral. To a bidder, an empty field and a bad value look identical, and both get priced as risk. This article is about which fields are worth filling, how to fill them without inventing anything, and how to tell afterwards whether it paid.

Table of Contents

What is bid request enrichment?

Bid request enrichment is the set of steps an exchange performs between receiving a request from supply and sending it to demand, in which fields the request did not carry are populated from information the exchange can derive or already holds.

It is worth separating from two things it gets confused with. It is not filtering — filtering decides whether to send a request at all, enrichment decides what the request says. And it is not optimization — floors, scoring and shaping decide what to do with a request, enrichment decides how completely it is described. On a well-built exchange these are three distinct stages with three different time budgets, and enrichment is the earliest and the cheapest: it runs before the auction opens, on every request, and it has to finish in well under a millisecond because it is pure overhead on a latency budget that belongs to the buyer.

The commercial case is simple enough to state in one line. A DSP’s bid decision is a series of eligibility checks — does this user belong to a segment I am targeting, is this geography in my flight, is this placement one my optimizer has learned to value — and every check that cannot be evaluated resolves the same way, which is no. Enrichment converts unanswerable checks into answerable ones. Some of them will answer no anyway. The ones that answer yes are bids you were not getting.

Why do bid requests arrive incomplete?

It is tempting to treat an underpopulated request as somebody’s negligence. It usually is not. Requests arrive thin for structural reasons, and knowing which reason applies tells you whether enrichment or a conversation with the publisher is the right fix.

The tag never had the data. A display tag in a browser knows its own page and very little else. It does not know the visitor’s city, and it does not know what your exchange calls this placement. Nobody dropped these fields; they were never available at the point of collection.

The upstream wrapper stripped it. Requests that reach you through a header bidding wrapper, a mediation SDK or another exchange have already been serialized and reconstructed at least once, and each hop keeps the fields it understands. Extended IDs are a common casualty — a source the original page carried can be absent three hops later simply because an intermediate implementation did not map it.

The publisher’s integration is old. imp.ext.gpid postdates a great many working integrations. Content categories and language are optional fields that plenty of ad servers never populate. An integration built in 2019 and never touched since is not broken, it is just missing everything standardized afterwards.

The environment is genuinely constrained. Connected TV and in-app requests can be rich in some dimensions and permanently blank in others. There is no cookie to sync in a CTV app, and no amount of engineering changes that.

The first three are recoverable by the exchange. The fourth is not, and pretending otherwise is how you end up with fabricated signal — which is the subject of a later section.

The rule that makes enrichment safe: fill blanks, never overwrite

Every enrichment step should obey one invariant: if the publisher supplied a value, the publisher’s value wins. The exchange fills blanks. It does not correct, improve, or replace.

This sounds like modesty. It is actually the property that makes the whole practice defensible, for three reasons.

It keeps the publisher authoritative about their own inventory. A publisher who sends a placement identifier means that identifier — it may key their own yield reporting, their own reconciliation, their own contracts. An exchange that silently replaces it has broken something it cannot see.

It makes the behaviour auditable. “We populate imp.ext.gpid when it is absent” is a claim a demand partner can verify by sending you a request with the field set and observing that it comes out unchanged. “We normalize placement identifiers” is a claim nobody can check, and in a diligence conversation it reads as evasion.

And it bounds the blast radius of your own bugs. An enricher that only ever writes to empty fields can, at worst, add something wrong. An enricher that overwrites can destroy something right, and it will do so silently, at scale, on inventory that was previously working.

The same invariant applies to identity with an extra wrinkle. When an exchange re-injects an extended ID it observed earlier, the inbound request wins every shared source — if the page sent a value for that ID source, the stored copy is dropped even if it is newer or looks more complete. Reconciling the two would mean deciding, per request, that you know this user better than the publisher does. You do not.

Geography: the cheapest field you are probably still dropping

Geographic resolution from the IP address is the least glamorous enrichment and usually the highest-yielding, because the gap between what arrives and what is derivable is enormous. A request typically arrives with an IP and, if you are lucky, a two-letter country. What is derivable from that same IP is country, region with its ISO code, city, latitude and longitude, metro code, postal code and UTC offset.

Every one of those is a targeting dimension somebody is buying on. Local and regional advertisers buy metro and postal geography and will not bid at all on a request that resolves only to a country. Time zone changes which dayparted campaigns are eligible — the same request at the same UTC instant is prime time in one region and the middle of the night in another, and a bidder that cannot compute local time cannot apply its schedule.

Two cautions, both about honesty rather than technique.

Geographic resolution is a probability, not a fact, and it degrades in a specific direction: country is highly reliable, city is decent, and precise coordinates derived from an IP are a centroid, not a location. Populate what the database actually returns. Do not synthesize precision by filling coordinates with the middle of the resolved city — some bidders will read those coordinates as GPS-grade and price them accordingly, and you will have manufactured a signal nobody can trust.

And resolution failures must stay failures. When the lookup does not find the address, the correct output is an empty result that leaves the fields blank, not a fallback to a default country. A silent fallback turns a measurable miss into an invisible lie, and it will show up months later as a bidder wondering why your Country X traffic never converts.

Identity: the field buyers pay the most for

Identity is the enrichment with the clearest link to revenue, for a reason we covered at length in our guide to cookie sync and match rate: the highest-value demand — retargeting, audience extension, frequency-managed brand spend, conversion-optimized performance budgets — is categorically ineligible on a user the buyer cannot recognize. It is not that those campaigns bid lower. They do not bid.

The enrichment surface has three layers, and most exchanges build only the first.

Pass through what arrives. The eids array in OpenRTB carries extended IDs — UID2, ID5, RampID, hashed email and others — as source-and-value pairs. Passing them through intact, to the partners entitled to read each source, is table stakes. The common failure is not dropping them but mangling them: consolidating user.eids and user.ext.eids inconsistently, or forwarding a source to a partner with no agreement to receive it.

Run the sync loop. Server-side and pixel-based user sync build the match tables that let you populate buyeruid per demand partner. This is a maintenance commitment rather than a project — match tables decay continuously, and the article linked above covers the mechanics.

Re-inject what you have seen before. This is the layer most exchanges skip, and it is pure recovered revenue. The same user reaches your exchange many times across many properties. Frequently a request arrives carrying an extended ID, and a later request for the same user — different page, different publisher, different wrapper — arrives without it, because that path never implemented that ID source. If you retained the association, you can restore the missing source on the second request.

The mechanism deserves its own guardrails. Re-injection is only defensible when inbound always wins on shared sources, when it is bounded by the same consent signals as any other identity processing, and when it is instrumented on both branches — count the requests where you restored something and the requests where nothing was missing. A re-injection counter with no matching no-op counter cannot distinguish “working perfectly, nothing to do” from “silently disabled three weeks ago.”

When the live sync misses: recovering a match offline

Live sync has a hard ceiling. It only learns about users who pass through a path where a sync could fire, in a browser that permitted it, recently enough that the entry has not expired. Everything else is a miss, and a large fraction of misses are not recoverable in real time at all.

They are recoverable offline. An identity graph — delivered on a schedule by an identity provider, or assembled from your own observations — maps identifiers to each other outside the request path, using evidence a live redirect never has: matches observed elsewhere in the provider’s network, matches inferred from network context, matches established through the provider’s own graph connections. The delivery arrives as a bulk file, is ingested into a store the auction can read in microseconds, and turns into matches on requests where the live sync would have found nothing.

This is the clearest illustration of the enrichment thesis, because the traffic is identical either way. The same user, on the same page, generates a request the exchange either can or cannot describe — and the difference was decided by a batch job that ran hours earlier.

Three engineering notes for anyone building it.

The graph is a fallback, not a source of truth. It fills gaps left by the live path; it never overrides an ID the request carried.

Carry the match method through to the buyer. OpenRTB’s extended IDs have a field for how a match was established, and the honest answer matters: a match from a completed cookie sync and a match inferred from network context are different qualities of evidence, and buyers are entitled to weight them differently. Collapsing both into one undifferentiated ID is the kind of shortcut that ends a demand relationship when it surfaces.

Gate it in the right places. Offline graph enrichment should be switchable per tenant, respect the platform’s data-processing settings, and be suppressed entirely when the request carries a privacy restriction. Those are three separate conditions, and they should be three separate checks with one counter each, so that a zero downstream is attributable to a specific gate rather than an ambiguous nothing-happened.

Placement identity: the field nobody sends and every buyer wants

This is the enrichment with the largest gap between how boring it sounds and how much it matters.

A DSP’s optimizer learns at the placement level. It builds performance history for a specific slot on a specific property and uses it to decide what that slot is worth. To do that, it needs a stable identifier that means the same thing tomorrow as it does today, and it needs that identifier to be stable across every path the inventory reaches it through.

What it usually gets instead is an ad unit code that is unique inside the publisher’s ad server and meaningless outside it, or a divisor string that changes when the site is redesigned, or nothing at all. OpenRTB’s answer is imp.ext.gpid — the Global Placement ID. It is the field designed to solve exactly this, and it is absent from an enormous share of live traffic.

An exchange is in an unusually good position to fill it, because the exchange knows things the page does not: which supply partner the request came from, which property it belongs to in the exchange’s own records, which ad format the impression is, and what size was requested. Composed in a fixed order, those produce an identifier that is deterministic — the same placement yields the same string on every request, for every demand partner, indefinitely.

The properties that make a synthesized placement ID useful rather than noise:

  • Deterministic. Derived from stable identifiers, never from anything per-request. A placement ID that changes between requests is worse than none, because the buyer’s optimizer will treat each request as a new placement it has never seen and has no history for.
  • Enrich-only. If supply sent a GPID, use theirs. A blank or whitespace-only value counts as absent; a real value is never touched.
  • Consistent across partners. Every demand partner sees the same identifier for the same slot. Per-partner identifiers destroy the entire point.
  • Degrades to nothing. When the components are not available — no property identifier, no format information — emit no GPID rather than a partial string. A malformed identifier that collides across placements is actively harmful; absence is merely unhelpful.

Of everything in this article, this is the enrichment most likely to be missing from your stack right now, and the one a demand partner is most likely to thank you for.

Context: name, categories, language, privacy policy

The last group is the least discussed and the easiest to fill, because it is mostly a matter of using what you already recorded during onboarding.

When you integrate a publisher you learn things about the property: its name, its content categories, its primary language, whether it has a privacy policy. Requests from that property routinely arrive without any of it — the tag has no reason to send what the exchange already knows.

Filling these fields serves buyers who filter on them. Brand safety and suitability targeting operates on content categories; a request with an empty cat array fails inclusion filters and lands in the same bucket as inventory that was actively categorized as unsuitable. Language targeting is a hard gate for advertisers running localized creative — the wrong-language impression is a wasted impression, so they will not bid without the field. The privacy policy flag is a compliance checkbox for a meaningful set of buyers.

Two boundaries. Categories should be added to whatever the request carried, deduplicated, not swapped for your stored set — the page may know about content the property-level record does not. And the categories must come from an actual record established during onboarding, not from guessing at the domain name. Which brings us to the section that matters most.

What enrichment must never do

Every mechanism in this article is a way of adding a field to a request. That capability, pointed slightly wrong, is indistinguishable from fraud — and the industry has enough experience with signal that turned out to be invented that buyers are actively looking for it.

The line is precise. Enrichment populates a field with a value you can substantiate. Anything else is fabrication, no matter how reasonable the guess.

Concretely, on the wrong side of that line:

  • Inventing device or user attributes you did not observe. If the request did not tell you the device type and you cannot derive it from something the request actually carried, the field stays empty. A plausible default is worse than a blank, because a blank is honest about being unknown.
  • Filling identity from proximity. Two requests from the same IP minutes apart are not evidence of the same user, and treating shared network context as an identity match — outside a graph explicitly built and disclosed to work that way — invents a person.
  • Upgrading confidence. Passing an inferred match as though it came from a completed sync, or an IP-derived centroid as though it were device geolocation, is a lie about provenance even when the value is right.
  • Defaulting on failure. Every lookup that can miss must be allowed to miss. Fallback values convert a measurable gap into an invisible error.
  • Enriching around a refusal. If a privacy signal says do not process this user’s data, that applies to the enrichment stage too. Restoring a stored ID for a user who withheld consent is not a technicality.

The test to apply before shipping any enricher: if a demand partner audited this field, could you show them where the value came from? If the answer involves the word “typically,” it is fabrication.

Enrichment and privacy compliance meet at exactly one question: does this specific processing have a basis for this specific user?

The mechanics are well-established — IAB TCF for European traffic, GPP as the multi-jurisdiction successor, COPPA flags, and the per-request restriction signals that ride alongside them. What matters architecturally is where the check sits. Consent must gate the enrichment stage itself, not the transmission afterwards. An exchange that resolves an identity, attaches it, and then strips it before send has already processed the data.

Three checks that belong on any enrichment path touching user data, kept separate rather than collapsed into one flag: whether the tenant has enabled this class of processing at all, whether the platform-level data settings permit it, and whether this individual request carries a restriction. Separate conditions with separate counters, so when enrichment stops happening you can tell which gate closed.

Content and placement enrichment sit outside all of this. A site’s language and a slot’s identifier are facts about inventory, not about a person — which is a good reason to build them first.

How do you prove enrichment actually paid?

Enrichment is unusually easy to measure honestly, because the population splits itself: some requests arrive with the field, some arrive without it and get enriched. That is very close to a natural experiment, and you should treat it as one rather than reaching for an industry benchmark.

Measure per field and per demand partner. Enrichment value is not a platform-level number. A partner buying only broad prospecting inventory may be indifferent to identity and highly sensitive to placement stability; the next partner is the reverse. A blended average across both hides the two facts you could have acted on.

Compare bid rate first, price second. Bid rate — the share of requests that draw at least one bid — is where eligibility changes show up, and eligibility is the mechanism enrichment operates on. Price effects are real but noisier and slower to establish.

Instrument the negative branch. For every enricher, count three outcomes: the field was already present, the field was absent and you filled it, the field was absent and you could not fill it. Only the third number tells you where the remaining opportunity is, and only the first two together tell you the enricher is alive. A single “enriched” counter is compatible with an enricher that has been failing closed for a month.

Beware the confound. Requests that arrive complete are not a random sample — they come from better integrations, which correlate with better inventory. So “enriched requests earn less than natively complete ones” is expected, and it is not evidence that enrichment failed. The comparison that answers the question is enriched-versus-unenriched within the same supply path, which usually means a holdout: leave a slice of eligible requests unenriched and compare. If a lever cannot be turned off, its contribution cannot be measured — a principle that applies with even more force to traffic shaping.

Key takeaways

Enrichment What it fills Why the buyer pays more
Geography region, city, metro, postal, time zone unlocks geo-targeted and dayparted budgets that will not bid on a country alone
Identity pass-through eids, buyeruid makes segment-targeted and retargeting line items eligible at all
Identity re-injection ID sources missing on this path recovers matches lost to wrapper hops and partial integrations
Offline identity graph matches the live sync never made converts permanently unmatched traffic into addressable traffic
Placement ID (imp.ext.gpid) a stable, cross-partner slot identifier lets the buyer’s optimizer accumulate history and value the slot
Content and context name, categories, language, privacy flag passes brand-safety, suitability and language gates instead of failing them

The five rules underneath the table: fill blanks and never overwrite; let failures stay failures; carry provenance so a buyer can weight your signal; gate on consent at the enrichment stage, not at send; and measure per field, per partner, against a holdout.

Running enrichment on Floxis

Floxis is a white-label RTB exchange you run as your own — your domain, your branding, your margin — and enrichment is not an add-on module, it is what happens to every request on its way through.

Floxis GeoIP resolves country, region, city, metro, postal code and time zone from the IP before the auction opens. User sync runs both server-side and as pixel sync, works with the IDs you already run — UID2, ID5, RampID, hashed email — and is backed by observed-ID re-injection so a request that loses an identifier to a wrapper hop does not reach demand anonymous. Placement identity is synthesized where supply omits it, deterministically and identically for every demand partner. Property-level context — name, categories, language, privacy policy — is filled from what onboarding already recorded. Every one of these is enrich-only: a value your supply partner sends is never overwritten by ours.

All of it sits behind the consent layer rather than beside it. IAB TCF and GPP are handled natively, identity enrichment is gated per tenant and suppressed on privacy-restricted requests, and Floxis is registered on the IAB Europe Global Vendor List as vendor 1609. And because enrichment outcomes land in the same real-time analytics as everything else — log-level, roughly a minute behind the event — the per-field, per-partner measurement this article argues for is a report you open rather than a pipeline you build.

Enrichment is the first of three layers, and the other two are worth naming because the distinction is where most “AI-powered exchange” marketing falls apart. Enrichment makes a request worth more, before the auction, in under a millisecond. Statistics — floors, scoring, blocking, shaping — decide what to do with it, inside the auction, within the 200–300 ms the buyer allows. Language models explain what happened, to you, afterwards. Nothing about that third layer runs in the auction path, deliberately: a language model cannot answer inside a 200 ms budget, and any vendor claiming one does is describing something that would not work.

If you are evaluating platforms rather than building, the enrichment questions belong on the checklist next to the ones in our white label ad exchange guide: which fields are enriched, whether enrichment ever overwrites supply, how consent gates it, and whether you can see the fill rate per field. Book a demo and we will run your own traffic mix against it.

Sources worth bookmarking