Identity Match Frequency-Cap Data Flow
This page describes how frequency-cap state reaches the Identity Match service and how Identity Match consumes it at eligibility time. It defines the data flow only — what crosses the boundary between the impression tracker and the Identity Match service. Internal mechanics (how the impression tracker counts impressions, where policies live, what storage layout the Identity Match service uses, how identities are deduplicated upstream) are buyer-internal concerns and are out of scope here. The wire spec lives in the TMP specification; the conformance invariants the Identity Match service must satisfy are also normative there. The reference implementation of the Identity Match cap-state store ships inadcp-go/targeting/fcap.
Roles
The split is deliberate: counting impressions, evaluating windows, and deciding when a cap fires are buyer-internal policy concerns that vary across buyers and across campaigns. The Identity Match service stays narrow — it answers “is this user currently capped on this package?” and nothing more. New cap dimensions (advertiser, campaign, creative — see extensions) plug into the same boundary contract without changing the service.
End-to-end flow
The cap-fire event
When a buyer’s policy evaluation determines that an impression has exhausted a cap, the impression tracker writes a cap-fire entry to the Identity Match cap-state store. Each entry consists of:
A single cap-fire event typically corresponds to one entry; a cap that fires on multiple resolved identities or multiple packages produces one entry per
(identity, package) pair, all sharing the same expire_at if the buyer’s policy is the same.
The cap-state store does not record per-impression counts, policy definitions, or window configurations. Its only job is to answer “is this (user_identity, package) currently capped?” The buyer’s policy logic — counting, windowing, choosing dimensions to cap on, deciding when to fire — lives entirely in the impression tracker.
The eligibility query
At query time, the Identity Match service receives a list of identities and a list of candidate packages. For each candidate package, it checks the cap-state store for any matching(identity, package) entry across the user’s identities. If any entry exists, the package is excluded from eligible_package_ids. This is a presence check, not a count.
Cap state is one input to eligibility. The Identity Match service also evaluates audience membership, package active state, audience freshness, and any other inputs the buyer cares about — see the conformance invariants. The cap-state portion of that evaluation is the part this page defines.
Policy updates and cap-state re-evaluation
Cap-state entries are written under whatever fcap policy was in force at cap-fire time. When the buyer’s fcap policies change — a window shortens or lengthens, amax_count rises or falls, a policy is paused or removed, a package is reassigned to a different policy — the existing cap-state entries written under the old policy can become stale. Stale entries either suppress users who should now be eligible (over-suppression) or fail to suppress users who should now be capped (under-suppression).
When a fcap rule changes, the buyer’s policy owner (typically the impression tracker or a service in its pipeline) MUST re-evaluate every cap-state entry the rule applied to and push the appropriate update to the IdentityMatch cap-state store. Two event shapes cover the cases:
Re-evaluation runs over the buyer’s own counting state (where impression history lives), not over the cap-state store — the cap-state store doesn’t carry counts. The output is the set of delete-or-extend events to apply.
The reference store in
adcp-go/targeting/fcap implements extend natively (a second RecordCap for the same (user_identity, field) overwrites the prior expire_at via HSETEX). Delete is a future extension — today, the simplest workaround is to extend with an expire_at already in the past, which causes the entry to be treated as absent at the next query and to be reaped by the backend’s TTL machinery.
Re-evaluation can be expensive when a policy applies to many users. Buyers typically run it asynchronously: enqueue the policy-change event, sweep the affected user population in batches, push delete/extend events incrementally. The protocol does not constrain the cadence — only the eventual consistency requirement that cap-state must converge to what the current policies imply.
Reference implementation
The cap-state store API inadcp-go/targeting/fcap is the reference shape. It exposes two operations:
Field is {SellerAgentURL, PackageID}. The reference store is backed by Valkey 9 hashes, hashed by user identity, with one hash field per (seller_agent_url, package_id) tuple and a TTL set to expire_at. Other backends (Aerospike, DynamoDB, in-memory, anything) are conformant if they satisfy the boundary contract above.
Future extensions
Today the cap-state store is keyed at(user_identity, seller_agent_url, package_id). Future protocol versions may extend the field to additional dimensions — advertiser, campaign, creative, line item — so a buyer can express caps that span multiple packages without writing N entries on every cap-fire. The boundary contract on this page is unchanged by such extensions: the impression tracker writes cap-fire entries; the Identity Match service checks presence at query time.
See also
- TMP Specification — wire spec, TMPX format, conformance invariants
- Impression Tracker Implementation Reference — non-normative reference for the impression-tracker side of the boundary (multi-identity dedup via
impression_id, fcap_keys label model, log-based reference data model, SDK primitives) - Buyer Guide — buyer agent integration, Context Match + Identity Match flows
- Migration from AXE — for buyers transitioning from AXE-shaped pipelines, including the OpenRTB User.eids cross-walk
- Privacy architecture — what each party learns
- Router architecture — provider registration, fan-out, latency
adcp-go/targeting/fcap— reference cap-state store in Go