The Role of Identity Modules in Prebid 9.0 and Beyond
Prebid.js 9.0.0 shipped on June 13, 2024, and it removed four identity sub-modules from the codebase in one release: Britepool, pirId, Parrable and Idward RTD. A publisher who upgraded and deleted the dead references lost those identifiers from every bid request, and no ad server report flags the absence. At one million monthly pageviews and above, a silent change in the identity layer is a revenue event, not a code hygiene task. Buyers pay differently for impressions they can recognize, and 9.0 changed both the consent plumbing that governs recognition and the module set that produces it.
The changes sort into three groups: consent plumbing, module removal, and the interface direction Prebid.js 10 has already set. Each group demands work in the wrapper build, not in the ad server. The sections below take them in order and close with the audit we run before any major Prebid version reaches a production build.
What an identity sub-module actually does
Every identifier in the stack follows the same short path, and knowing the path makes every 9.0 change legible. The wrapper registers a sub-module from the User ID family, the module checks consent, calls the vendor endpoint that mints or refreshes the ID, stores the result, and attaches it to bid requests as userIdAsEids and in user.ext.eids. Demand partners match those structures against their own user data and adjust bids accordingly. The Prebid identity overview counts more than 20 different flavors of global IDs supported by the User ID module, from cross-site systems to publisher-registered IDs, which means the module list you enable, not the framework, defines your identity coverage. The framework handles consent and transport; the sub-module list carries the yield.
Each sub-module brings its own config block, and two fields decide most of its production behavior: the storage settings and the vendor parameters. Storage settings declare the type (cookie or local storage), the name of the stored key and the expiry; the expiry counts in days, and it controls how long the vendor avoids re-minting. Get the fields right and the module costs roughly one call per user per expiry window; get them wrong and the module either never fires or fires on every page. Most identity incidents trace back to those two fields rather than to vendor endpoints.
What Prebid 9.0 changed under the hood
The headline consent change was a rename: 9.0 renamed the GDPR modules to TCF modules. The new names match what the modules enforce, the IAB TCF framework, rather than the regulation that inspired the old ones. Runbooks, wrapper logic and internal dashboards that reference the old names should move in the same commit as the upgrade, or the docs and the codebase drift apart.
Two more consent changes landed with the rename. The release removed GPP 1.0 support from the GPP module, so a CMP still writing GPP 1.0 sections loses that handling on upgrade; confirm the GPP version your CMP emits before you ship. And 9.0 moved vendorless modules such as sharedid from vendor consent to publisher purpose consent in the TCF object. A vendorless ID has no entry in the global vendor list, which makes publisher purposes its only route to consent; if your CMP hides those purposes or denies them by default, sharedid goes dark while every registered vendor in the build still shows grants.
Module removal was the louder half of the release. Prebid 9.0 removed modules including the identity sub-modules Britepool, pirId, Parrable and Idward RTD, and publishers building with them needed replacements or removal. The failure mechanics depend on the mistake: leave a removed name in the config array and the build fails loudly at bundle time; delete the name and the build passes while the identifier disappears from every bid request. The official Prebid 9.0 release notes list every removed module and every consent change, and that list should drive your upgrade diff, not memory.
Consent gating: when the User ID module does nothing
The User ID module sits in front of every identifier your build carries, and it checks consent before it does anything else. If no consent string exists, or the user has not consented to Purpose 1, local storage, the module skips the vendor URL calls and skips ID storage entirely. No Purpose 1 means no network traffic to ID vendors, no cookies, no local storage entries, no identifiers on the request. On EEA and UK traffic this is the normal state for a meaningful share of sessions, so size identity expectations against the consented share of your audience rather than against total traffic.
Storage configuration then decides whether an ID survives the page view. You set storage expiry in days; with it set, the module persists each ID for that period. Without it, the failure modes split by storage type: cookie mode falls back to session cookies that die with the browser session, and local storage mode creates new IDs on every page. A per-page ID gives buyers nothing to recognize and wastes the vendor call that minted it. The User ID module reference documents the exact consent and storage behavior; audit every sub-module config for an explicit expiry in days instead of inheriting defaults.
Verify the gate in devtools rather than trusting config review. Load a page with consent granted, count the vendor requests, close the browser, reopen it and confirm the stored ID still reads the same value. Then load with consent denied and confirm zero vendor requests and zero new storage entries. The test takes minutes per sub-module and it catches both silent failure modes: skipped calls and per-page IDs.
Latency follows the same gate. Vendor calls fire only once consent permits them, and storage amortizes them across the expiry window, so the first consented page view pays the cost and later page views read from storage. Open the network panel after a consent grant and map each request to an ID vendor; any request you cannot connect to a paying buyer is latency without yield.

What the identifiers do at auction
Identity modules act on the bid request, not on the auction. The User ID module writes resolved identifiers into the request as userIdAsEids for adapters and into user.ext.eids for demand partners reading OpenRTB, and each buyer matches what it can against its own user store. Recognition lets a buyer apply frequency caps, purchase history and audience membership instead of guessing from page context and geo. A buyer that cannot recognize the user bids on content and price alone, which is exactly where bid shading bites hardest. The mechanics of first-price auctions and bid shading sit one level below identity; the question identity has to answer is narrow. Does the marginal sub-module raise CPMs on the traffic where its IDs resolve? If the answer is no, drop the module.
Demand-side behavior decides whether an ID earns its slot. Buyers pay for verified audiences on curated supply paths through OpenRTB 2.6 seats that read eids directly, and an identifier no buyer matches is pure latency. That test, demand recognition followed by a measurable price difference, is the one we apply before any sub-module enters a publisher build.
Measuring a sub-module is straightforward if the comparison stays honest. Compare CPMs on requests that carried a resolvable ID against requests from the same geo and format that carried none, over a window long enough to absorb weekly patterns. Toggle one sub-module at a time; identity modules interact, and a module can look valueless in a bundle test while carrying clear value alone. Treat the per-geo result as the primary read and the global average as a summary nobody acts on.
The same identifiers travel through server-side seats. We run header and server-side demand, and the IDs resolved in the page flow into the OpenRTB 2.6 requests our seats send upstream. Fragmentation between channels loses recognition: if the server-side request carries no eids, buyers on those seats bid as if the user were new. Keeping the identity payload consistent across channels belongs in the migration plan, not in a postmortem.
The regional split: where the UID2 module goes quiet
Identity has geography now, and the official UID2 Prebid module is the sharpest example. The module checks consent data and does not operate if the gdprApplies flag is true, which covers EEA and UK traffic. US audiences can carry UID2 identifiers into bid requests; EEA and UK audiences cannot, at least through the official module. Configure sub-module sets per geo and measure per geo. A global average blends a working US identity program with a dead EEA one and reports that the program half works, the most expensive kind of average in the stack.
The realistic EEA set is smaller: TCF-registered vendors that users actually grant, vendorless IDs running on publisher purpose consent, and first-party identifiers you own end to end. None of those reach US match rates, so the EEA yield conversation centers on content quality and first-party data rather than on replicating US identity coverage. Report identity health in separate cuts for the US, EEA with consent, and EEA without, because the three cuts behave like three different products even when the build is identical.
First-party identity is the EEA hedge. Identifiers you mint from logins or newsletter signups live under the purposes users grant you directly, travel in the same eids structures, and survive the third-party cookie decline that hollowed out cross-site IDs. They also cost product work rather than config work: capture points, consent language and refresh logic all live on your side. Publishers with meaningful logged-in traffic should route that traffic into the bid request before adding another third-party ID vendor to the build.
Prebid 10 already closed an interface you may still use
Prebid.js 10 made the direction explicit, and two changes should shape your upgrade plan now. First, Prebid.js 10 stops populating bid.userId; bid modules should rely on userIdAsEids or user.ext.eids. Analytics adapters, bid adjusters and revenue monitors that read bid.userId receive undefined after the upgrade, and they receive it silently. Search the codebase for bid.userId and move every read to the eids structures while 9.x still runs, because the new reads work in both versions and let you ship early. Second, the 10.0 release swapped verizonMediaIdSystem for yahooConnectId. Config carrying the old system name needs the rename, and the rename belongs in the same tested release as the bid.userId migration.
Sequence the migration in two releases. In the first, ship the bid.userId to eids migration while still on 9.x and watch reporting for a week; both versions populate the new fields, so metrics should hold flat. In the second, bump to Prebid.js 10 with the verizonMediaIdSystem rename bundled in. A migration split into observable steps rarely rolls back; a bundled surprise often does.

Running identity across thousands of builds
At OnlyMedia we run this stack daily. We are an ad network, an SSP and an agency in one company; we integrate OpenRTB 2.6 seats and Prebid for header and server-side demand; and we work with thousands of publishers, each above one million monthly pageviews, through a curated programmatic demand network that pays Net 30, CPM based. Identity at that scale is a measurement problem before it is a configuration problem.
Three practices carry most of the value. We audit each sub-module against the buyer's question: which demand recognizes this ID, and what does that demand pay when it does? We split every identity metric by geo and consent state, because a module can look healthy in aggregate while returning nothing where GDPR applies. And we retire modules on a schedule rather than on a hunch: a sub-module that no longer resolves on our publishers' traffic adds vendor calls and consent surface for zero return. Publishers who work with us see the outcome in a ten minute reporting console, with curated demand and Net 30 payments behind it, which turns identity experiments into decisions instead of lore.
Agency clients get the same audit, because advice about identity should match the buying behavior it predicts. When we recommend a module, the recommendation comes from the demand we actually run: the seats that read eids are the seats that pay differently for them. That closes the loop most stacks leave open, where a consultant recommends an identifier nobody downstream recognizes.
The audit checklist
This is the audit we run at OnlyMedia before a major Prebid version lands on a publisher build. It fits one working session and it covers every failure mode above.
- Diff the module list. Compare every identity sub-module in your build against the 9.0 removals: Britepool, pirId, Parrable and Idward RTD need replacement or deletion.
- Update consent-side names. Replace references to the old GDPR module names with TCF module names across runbooks, wrappers and internal tooling.
- Check the GPP version. Your CMP must emit a version the current GPP module supports; GPP 1.0 support is gone.
- Expose publisher purposes. Vendorless IDs such as sharedid now run on publisher purpose consent, so verify that your CMP passes those purposes into the TCF object.
- Set expiry in days. Every sub-module needs an explicit storage expiry; without it, cookie mode drops to session cookies and local storage mode mints a new ID per page.
- Split reporting by geo and consent. A module that only runs where gdprApplies is false, such as the official UID2 module, looks half dead in aggregate and fully alive per geo.
- Retire dead weight. Disable any sub-module that no demand partner recognizes; the vendor calls and storage entries carry real cost.
- Migrate the bid interface. Move every bid.userId read to userIdAsEids or user.ext.eids, and rename verizonMediaIdSystem to yahooConnectId in the same tested release as the Prebid.js 10 jump.
Identity modules are the only part of the header bidding stack where a one-line config error changes what demand partners can see while every dashboard on your side stays green. Prebid 9.0 tightened the consent rules and shrank the module set; Prebid.js 10 tightens the request interface next. Publishers who treat each major version as an identity audit keep the identifiers buyers pay for and drop the ones that only add latency. That trade benefits both sides of the market: demand gets impressions it can value, and the payment side stays simple, CPM based, Net 30, no sliding terms.