Skip to content

RFC: Prebid Server TMP integration architecture #2203

@bokelley

Description

@bokelley

Summary

Architecture spec for embedding TMP in Prebid Server. TMP runs as a built-in library — no standalone router service. This RFC captures the preliminary design for how providers, identity resolution, and TMPX exposure tokens integrate with the Prebid/GAM stack.

Context

PR #2079 introduces TMPX exposure tokens and country-partitioned identity routing. The Go implementation in adcp-go adds these to the router, client, and protocol types. This RFC addresses the next layer: how the router embeds in Prebid Server and how TMPX tokens flow through GAM.

Key Design Decisions

TMP is a Prebid Server capability, not a Prebid.js module

The TMP router embeds in Prebid Server as a Go library (github.com/adcontextprotocol/adcp-go/router). Zero external dependencies. Prebid Server handles fan-out, merge, and KV generation. Prebid.js passes targeting KVs to GPT — no TMP-specific client code needed.

Browser
  │
  ├── Prebid.js (no TMP-specific module)
  │     ├── UID2 / EUID module (existing)
  │     ├── LiveRamp ATS module (existing)
  │     └── passes server KVs to GPT (existing behavior)
  │
  ▼
Prebid Server (Go)
  │
  ├── TMP library (embedded)
  │     ├── context match fan-out → buyer context agents
  │     └── identity match fan-out → buyer identity agents
  │
  ├── LiveRamp Envelope sidecar (identity resolution)
  │
  └── Other modules (OpenRTB adapters, etc.)
  │
  ▼
GAM (via GPT)
  ├── Line item selection on adcp_pkg KVs
  └── %%tmpx_*%% macro substitution at serve time

Provider config carries macro_name

Each provider entry needs a macro_name field that controls the GAM targeting key for that provider's TMPX token:

tmp:
  providers:
    - id: scope3
      endpoint: https://us.tmp.scope3.example/v1
      context_match: true
      identity_match: true
      countries: [US]
      uid_types: [uid2, rampid, id5]
      macro_name: tmpx_scope3
      timeout: 30ms

    - id: scope3-eu
      endpoint: https://eu.tmp.scope3.example/v1
      context_match: true
      identity_match: true
      countries: [DE, FR, IT, ES, NL, GB]
      uid_types: [euid, id5]
      macro_name: tmpx_scope3
      timeout: 30ms

    - id: acme
      endpoint: https://tmp.acmeoutdoor.example/v1
      context_match: true
      identity_match: true
      countries: [US]
      uid_types: [uid2]
      macro_name: tmpx_acme
      timeout: 30ms

Regional clusters share a macro_name. scope3 and scope3-eu are the same buyer — GAM doesn't care which cluster generated the token. The router selects the right cluster by country; the TMPX flows through the same GAM macro.

The naming chain is two configs:

Prebid Server provider macro_name: "tmpx_scope3"
                                        │
                                        ▼
GAM creative tracking URL:         %%tmpx_scope3%%

The sales agent surfaces the macro name during create_media_buy so the buyer knows what to put in their creative tracking URLs.

Identity resolution sequencing

TMP identity match needs a resolved user token. Several identity sources feed into user.ext.eids:

Identity type Source How it reaches Prebid Server
UID2 UID2 module in Prebid.js user.ext.eids on the bid request
EUID EUID module in Prebid.js user.ext.eids
RampID LiveRamp ATS.js → Envelope sidecar Sidecar resolves cookie → RampID, injected into user.ext.eids
ID5 ID5 module in Prebid.js user.ext.eids

The LiveRamp Envelope sidecar runs alongside Prebid Server and resolves the LiveRamp envelope cookie into a RampID. This must complete before TMP fan-out starts, since the resolved RampID may be the best identity to send.

Prebid Server picks the UID from user.ext.eids that the most TMP providers support for the user's country.

TMPX flow through GAM

Buyer identity agent
  generates TMPX token              "k1.dGVzdC10b2tlbg..."
      │
      ▼
Prebid Server (embedded router)
  collects into tmpx_providers      {"scope3": "k1.dGVzdC10b2tlbg..."}
  maps to macro_name                tmpx_scope3 = k1.dGVzdC10b2tlbg...
  sets as page-level targeting KV
      │
      ▼
Prebid.js → GPT → GAM
  passes KVs through (no TMP logic)
      │
      ▼
GAM at serve time
  substitutes %%tmpx_scope3%%       → k1.dGVzdC10b2tlbg...
      │
      ▼
Buyer impression pixel
  receives token, decrypts with cluster master key
  logs per-user exposure, updates frequency state

Targeting KV structure

Prebid Server sets two types of KVs:

Scope Keys Purpose
Ad unit adcp_pkg, adcp_seg, adcp_* (offer macros) GAM line item selection
Page tmpx_{macro_name} GAM macro substitution at serve time

Page-level for TMPX because it's per-provider, not per-ad-unit. All slots on the page share the same tokens.

Onboarding a new buyer

Config only, no code changes:

  1. Publisher ops adds provider to Prebid Server config (id, endpoint, countries, uid_types, macro_name)
  2. Sales agent creates media buy, tells buyer their macro is %%tmpx_{macro_name}%%
  3. Buyer sets up creative tracking URL with that macro

Request flow

1. Prebid.js sends auction request
     user.ext.eids populated by UID modules (existing)

2. Prebid Server enrichment:
     a. LiveRamp sidecar resolves envelope → RampID (if needed)
     b. TMP runs parallel with OpenRTB bid requests:
          - Picks best UID for this country from user.ext.eids
          - Context match: page context + packages → buyer context agents
          - Identity match: user token + country → buyer identity agents
          - Router filters by country + uid_type, strips country
          - Merges: eligibility (AND semantics), TMPX tokens per provider

3. Sets targeting KVs on auction response
4. Prebid.js passes KVs to GPT → GAM
5. GAM selects line items, substitutes %%tmpx_*%% at serve time
6. Impression pixel fires with TMPX

Timing

0ms   ── Prebid.js sends auction request
        ├── LiveRamp sidecar resolves envelope (~10ms, if needed)
5ms   ── TMP fan-out: context + identity in parallel
        ├── (parallel with OpenRTB bid requests)
25ms  ── TMP results ready, KVs set on response
30ms  ── Prebid.js passes KVs to GPT → GAM
100ms ── Creative rendered with %%tmpx_*%% substituted
~200ms── Impression pixel fires

Error handling

Failure Behavior
TMP fan-out unreachable No TMP KVs, GAM uses non-TMP line items
One provider times out Other providers' packages activate, missing TMPX dropped
LiveRamp sidecar down No RampID, TMP uses other UIDs from eids
No UID available Context match only, no identity match, no TMPX
GAM macro empty %%tmpx_scope3%% renders empty, buyer pixel handles gracefully

Open Questions

  1. Should macro_name be part of the AdCP protocol spec (on the ProviderEntry in product.json), or purely a publisher-side config in Prebid Server?
  2. UID priority: Should the protocol define a canonical ordering, or is "most provider coverage" the right heuristic?
  3. Consent passthrough: How do TCF/GPP consent strings flow from Prebid's consent management to TMP's ConsentSignals?

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.rfcProtocol change — auto-adds to roadmap board

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions