> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# TMP for Web Publishers

> How TMP activates pre-negotiated packages on web pages using Prebid and GAM.

# TMP for Web Publishers

Web publishers typically run an ad server (GAM, Kevel, FreeWheel) that manages line items, targeting rules, and ad selection. TMP integrates with this infrastructure through a Prebid module — it tells the ad server which deals to activate, it does not replace the ad server.

## How It Works Today

A publisher running Prebid with AdCP deals has packages defined through `create_media_buy`. To activate them, the publisher manually creates corresponding line items or PMP deals in their ad server. A vendor-specific RTD module injects targeting signals by sending the full OpenRTB BidRequest to the vendor's API. This works but requires per-vendor integration and sends unnecessary data.

TMP replaces vendor-specific RTD modules with a single Prebid module that speaks a standard protocol. Buyer agents evaluate context and identity separately, and the publisher joins the results locally before passing instructions to GAM.

## Context Match

When a page loads, the TMP Prebid module sends a Context Match request to the router. The request includes the page context. No package list is sent — the provider uses its synced package set for this placement.

### Context Match Request

```json theme={null}
{
  "type": "context_match_request",
  "request_id": "ctx-7f2a-oakwood-91b3",
  "property_rid": "01916f3a-9c4e-7000-8000-000000000010",
  "property_id": "oakwood-publishing-main",
  "property_type": "website",
  "placement_id": "article-sidebar-300x250",
  "seller_agent_url": "https://oakwood.example",
  "artifact_refs": [
    { "type": "url", "value": "https://oakwood.example.com/sustainable-kitchen-2026-03" }
  ]
}
```

Key points:

* No package list is sent per request. The provider evaluates all eligible packages for this placement using its synced package set from media buy setup. The same packages are evaluated for every user, preventing identity leakage into the context path and enabling response caching.
* `artifact_refs` references content by type and value. The buyer agent resolves artifact metadata from its cache — no inline signals needed on the request.

### Context Match Response

The router fans out to each buyer agent and merges the responses. Each buyer returns offers for packages whose targeting matched the content context, plus response-level signals that flow through to GAM as key-values.

```json theme={null}
{
  "type": "context_match_response",
  "request_id": "ctx-7f2a-oakwood-91b3",
  "offers": [
    { "package_id": "pkg-display-0041" },
    { "package_id": "pkg-native-0078" }
  ],
  "signals": {
    "segments": ["sustainability", "home_cooking"],
    "targeting_kvs": [
      { "key": "adcp_seg", "value": "sustainability" },
      { "key": "adcp_seg", "value": "home_cooking" },
      { "key": "adcp_pkg", "value": "pkg-display-0041" },
      { "key": "adcp_pkg", "value": "pkg-native-0078" }
    ]
  }
}
```

Key points:

* `offers` contains one entry per activated package. The provider's synced set for this placement included three packages — these two matched the kitchen/sustainability context, the third (`pkg-display-0103`) did not and is absent.
* For web/GAM activation, offers are simple — just `package_id`. Richer fields (`brand`, `price`, `summary`, `creative_manifest`, `macros`) are available for integrations that need them but are not required.
* `signals.targeting_kvs` are the key-value pairs that the Prebid module sets on the GAM ad request. GAM line items are configured to match on these keys.

## Identity Match

Separately, the TMP Prebid module sends an Identity Match request with the user's identity tokens and the publisher's `seller_agent_url`. This request carries no page context. The buyer resolves its active package set from `seller_agent_url`; when the module sends `package_ids` explicitly (as below), composition MUST be independent of the current page — either all-active (every active package for that buyer at this publisher) or fuzzed (a random sample padded with synthetic non-existent IDs the buyer will silently drop). The page-specific subset is forbidden.

### Identity Match Request

```json theme={null}
{
  "type": "identity_match_request",
  "request_id": "id-3k9p-oakwood-d4f1",
  "seller_agent_url": "https://oakwood.example",
  "identities": [
    { "user_token": "tok_uid2_8f2a3b7c", "uid_type": "uid2" },
    { "user_token": "XY1a2b3c4d5e6f7g8h9i0jKlMnOpQrSt", "uid_type": "rampid" },
    { "user_token": "ID5*aB3xY9kL...", "uid_type": "id5" }
  ],
  "package_ids": [
    "pkg-display-0041",
    "pkg-display-0042",
    "pkg-display-0043",
    "pkg-native-0078",
    "pkg-native-0079",
    "pkg-display-0103",
    "pkg-display-0104",
    "pkg-video-0201"
  ]
}
```

Key points:

* `request_id` is unrelated to the context match `request_id`. The two must not be derivable from each other.
* `package_ids` example shows all-active mode: every active package for the buyer across the entire site, not just the three that were on this placement's context match. The page-specific subset is forbidden — it would let the buyer correlate identity with context by comparing package sets. Fuzzed mode (a random sample padded with synthetic IDs the buyer silently drops) is also acceptable.
* `identities` carries every token the publisher has available (UID2, ID5, LiveRamp, hashed email, publisher first-party). Each token is opaque — the buyer cannot reverse it to PII. Sending the full set maximizes match rate because different buyers resolve on different graphs.

### Identity Match Response

The buyer evaluates the user against all requested packages and returns the IDs of eligible packages, plus a TTL defining how long the router can cache this response.

```json theme={null}
{
  "type": "identity_match_response",
  "request_id": "id-3k9p-oakwood-d4f1",
  "eligible_package_ids": [
    "pkg-display-0041",
    "pkg-display-0042",
    "pkg-native-0078",
    "pkg-native-0079",
    "pkg-display-0104"
  ],
  "serve_window_sec": 60
}
```

Key points:

* Only eligible packages are listed. Packages absent from the list (e.g., `pkg-display-0043`, `pkg-display-0103`, `pkg-video-0201`) are ineligible. The buyer computes eligibility from frequency caps, audience membership, purchase history, and any other identity-based signals. The reasons are opaque to the publisher.
* `serve_window_sec` tells the router how long to cache this response. During that window, the router returns cached eligibility without re-querying the buyer. The publisher uses cached eligibility to allocate across all placements on the page.
* There is no `frequency_capped`, `audience_match`, or `recency` field. The buyer's internal reasons stay with the buyer.

## Activation: Joining Context and Identity

The publisher joins context match and identity match locally. This is where the two halves come together — the router never sees both at the same time.

### Step 1: Intersect

Take the offers from context match and filter by identity match eligibility.

| Package            | Context Match | Identity Match | Result   |
| ------------------ | ------------- | -------------- | -------- |
| `pkg-display-0041` | Offered       | Eligible       | Activate |
| `pkg-native-0078`  | Offered       | Eligible       | Activate |
| `pkg-display-0103` | Not offered   | Not eligible   | Skip     |

Only two packages survive: `pkg-display-0041` and `pkg-native-0078`.

### Step 2: Set GAM Targeting

The Prebid module takes the context match `signals.targeting_kvs` and sets them on the GAM ad request as key-value pairs:

```
adcp_seg = sustainability, home_cooking
adcp_pkg = pkg-display-0041, pkg-native-0078
```

GAM line items are pre-configured to target on `adcp_pkg` values. When the ad request arrives with `adcp_pkg=pkg-display-0041`, GAM matches it to the corresponding line item and serves the creative.

### Step 3: GAM Selects

GAM applies its own priority rules, competitive exclusions, and pacing logic across all eligible line items — including both TMP-activated deals and other demand sources. TMP does not override GAM's ad selection; it provides the inputs.

## GAM Line Item Configuration

For each active package, the publisher creates a GAM line item targeting `adcp_pkg = <package_id>`. This is the link between TMP activation and GAM ad selection.

* **Line item type.** Sponsorship or Standard, depending on deal terms from `create_media_buy`. Guaranteed deals use Sponsorship; non-guaranteed deals use Standard.
* **Priority.** Set based on deal type. Guaranteed deals at priority 4-8, non-guaranteed at priority 12-16. This determines how TMP demand competes with other line items in GAM's selection logic.
* **Creative assignment.** Reference pre-synced creatives from `sync_creatives`, or use the creative manifest from the Context Match response if the buyer provides one.
* **Lifecycle.** When a media buy ends or is cancelled, deactivate the corresponding line item. The router stops including the package in Identity Match within 1 hour.
* **Automation.** Publishers can automate line item creation by listening for new `create_media_buy` completions and mapping package details to GAM API calls. The package ID, deal type, priority, and creative references are all available from the media buy response.

## Prebid Integration

The TMP Prebid module is a Real-Time Data (RTD) module that replaces vendor-specific RTD modules. It is defined by the [TMP Prebid proposal](/specs/prebid-tmp-proposal) and follows the standard Prebid RTD module interface. It handles the full flow:

1. **On auction init**: Send Context Match request to the TMP router for each placement on the page.
2. **On context match response**: Store offers and targeting signals.
3. **After temporal decorrelation**: Send Identity Match request with the user's identity tokens and ALL active package IDs for each buyer. Decorrelation has two parts — a random 100-2000ms delay AND randomized order: each auction has roughly equal probability of the Identity Match request being sent first or the Context Match request being sent first.
4. **On identity match response**: Join with context match results. Set targeting key-values on the ad unit.
5. **On bid request**: GAM receives the enriched ad request with TMP targeting keys and selects line items as normal.

The temporal decorrelation between context and identity requests is a privacy measure. Random delay alone is insufficient — fixed ordering (Identity always after Context) leaks the pairing through ordering. The module randomizes both the delay and which request is sent first.

### Impression ID Substitution

In the web/Prebid surface the **Prebid TMP module is the decision layer** in the three-layer [`{IMPRESSION_ID}` minting hierarchy](/docs/creative/universal-macros#impression-identification) — publisher first, decision layer second, buyer-at-TMPX-decode last. The module checks whether the publisher has already supplied an impression\_id (via `adUnit.tmp.impressionId` or an equivalent first-party hook); if so, it passes that value through. If not, the module mints its own. Either way, it exposes the value on the GAM ad request as the targeting key `tmp_impression_id`. This is the value the buyer's impression pixel uses as the cross-identity dedup key — essential for context-only impressions where `{TMPX}` is absent, and useful even when `{TMPX}` is present so the buyer's dedup path stays uniform.

**Format is implementation choice.** The Prebid module MAY use ULID, UUID (any version), or any collision-resistant identifier scheme — the protocol does not pin a format.

**Optional optimization.** When Prebid's `enableTIDs` config is `true`, the module MAY reuse `adUnit.transactionId` as the impression\_id rather than minting a separate one. Since `enableTIDs` is opt-out in Prebid.js 8+ (for privacy reasons), most deployments need to mint their own.

```javascript theme={null}
import { ulid } from 'ulid';

// In the TMP Prebid module, at auctionInit.
// Resolve the impression_id in priority order:
//   1. publisher pre-mint (e.g., server-side, attached as adUnit.tmp.impressionId)
//   2. Prebid transactionId (decision-layer reuse) when enableTIDs is on
//   3. fresh decision-layer mint (ULID, UUID, or any collision-resistant scheme)
function resolveImpressionId(adUnit) {
  if (adUnit.tmp?.impressionId) {
    return adUnit.tmp.impressionId;  // publisher-side mint — highest priority
  }
  if (pbjs.getConfig('enableTIDs') && adUnit.transactionId) {
    return adUnit.transactionId;     // reuse Prebid's tid when available
  }
  return ulid();                     // decision-layer fresh mint
}
const impId = resolveImpressionId(adUnit);
pbjs.setTargeting(adUnit.code, { tmp_impression_id: impId });
```

The buyer's creative tracking URL, trafficked through GAM, references the targeting key via the standard GAM macro:

```
https://buyer.example/imp
  ?imp_id=%%PATTERN:tmp_impression_id%%
  &tmpx=%%PATTERN:tmp_tmpx%%
  &cb=%%CACHEBUSTER%%
```

At render, GAM substitutes the KVs and the buyer's impression tracker receives the impression\_id as an opaque string. The KV name `tmp_impression_id` deliberately avoids the `hb_*` prefix (Prebid's own namespace) and mirrors the `adcp_*` convention used elsewhere on this page for TMP-emitted targeting.

## Sequence Diagram

```
Page Load
  |
  |-- Prebid TMP Module ---> TMP Router (Context)
  |                              |-- fan out --> Buyer Agent A
  |                              |-- fan out --> Buyer Agent B
  |                              |<- merge <--- offers + signals
  |<--- Context Match Response --
  |
  |   (100-2000ms random delay; Context/Identity order randomized
  |    per auction — diagram shows Context-first; Identity-first
  |    runs the two phases in reverse)
  |
  |-- Prebid TMP Module ---> TMP Router (Identity)
  |                              |-- fan out --> Buyer Agent A
  |                              |-- fan out --> Buyer Agent B
  |                              |<- merge <--- eligibility
  |<--- Identity Match Response --
  |
  |-- Join locally: intersect offers with eligibility
  |-- Set targeting KVs on GAM ad request
  |-- GAM selects and serves ad
```

## Coexistence with OpenRTB

Most publishers run TMP alongside Prebid header bidding, which sources demand via OpenRTB. The two systems are complementary.

* **TMP as additional demand.** TMP packages appear as line items in GAM, competing with Prebid line items on priority and price. GAM handles the yield decision — TMP does not replace Prebid, it adds pre-negotiated demand.
* **Competitive exclusion.** Configure GAM competitive exclusion rules to prevent conflicting brands from appearing together across TMP and Prebid demand sources.
* **Revenue attribution.** TMP-activated impressions are tracked via `get_media_buy_delivery`, while Prebid impressions flow through existing SSP reporting. Publishers reconcile in their BI layer.
* **Timeout independence.** TMP and Prebid requests run in parallel. TMP timeout (50ms) is typically faster than Prebid timeout (1000-1500ms), so TMP results are ready before GAM needs them.

## Privacy Constraints for Web

These constraints apply to all surfaces but are worth restating for the web case:

* **Context match carries no user data.** No cookies, no user tokens, no IP addresses. The provider evaluates the same synced package set for every user on a placement.
* **Identity match carries no page data.** No URLs, no content signals, no placement IDs. The `package_ids` list, when sent, has composition independent of the current page (all-active or fuzzed) — never the page-specific subset.
* **The publisher joins locally.** The router never sees both context and identity for the same impression. Only the publisher, who already has both the page context and the user identity, performs the intersection.
* **Temporal decorrelation.** A random delay between the two requests AND a randomized order (Context Match or Identity Match equally likely to be sent first) prevent timing- and order-based correlation at the network level. Random delay alone is insufficient because a fixed order leaks the pairing through ordering.
