Skip to main content
Universal macros enable buyers to include dynamic tracking data in their creatives without needing to know each publisher’s ad server implementation details. Macros are placeholders that get replaced with actual values at impression time. Universal semantics and source syntax are separate. {CACHEBUSTER}, [CACHEBUSTING], and a vendor token can share a cache-busting meaning while requiring different resolvers and encoding. Never translate from spelling alone.

Overview

When you provide creative assets to AdCP, you can include universal macro placeholders in:
  • Impression tracking URLs
  • Click tracking URLs
  • VAST tracking events
  • Landing page URLs
Example:
At impression time, this becomes:

Declared source-token contracts

Delivery assets may attach macro_declarations[]. Each entry describes one occurrence of one exact source token. Company names are not dialect values: dialect is the small family enum adcp | iab_vast | iab_daast | vendor | unknown; IAB and vendor entries add an authority-controlled dialect_namespace and immutable dialect_revision. The namespace identifies the registry, while the revision pins the exact mapping bytes. For the live IAB VAST macro registry, use its canonical published registry URL as the namespace and an immutable source commit or content digest as the revision; a VAST document version is not a macro-registry revision because the registry evolves independently. Compatibility requires exact namespace and revision equality—implementations never treat a moving branch name or “latest” content as the revision. Translation and value resolution are different operations. translate_to_native changes an AdCP token into a declared downstream token without inserting a runtime value. The translated asset replaces the old declaration with the supplied translation_target. resolve_value inserts a concrete value using the exact encoding profile. preserve performs no mutation. The first contract permits one translation hop followed by value resolution; translation_target.next_operation is therefore fixed to resolve_value, which prevents cycles and unbounded chains. On the sales execution path, a translation is compatible only when both seller-wide and selected-product sets contain the source translation tuple and an exact target resolution tuple. On build_creative, preview_creative, and validate_input, the selected creative.supported_formats[] route is the authoritative capability set and build-time operations use performed_by: creative_agent; no seller authority is borrowed. The emitted target declaration keeps the source declaration_id, location, required, and unavailable_behavior, and replaces token, dialect identity, operation, actor, and encoding from translation_target.
The click redirect’s depth of two is part of the contract, not an implementation hint. rfc3986 encodes UTF-8 bytes outside the RFC 3986 unreserved set once per depth. iab_vast_uri follows the cited VAST registry’s one-pass rules, including array serialization and unavailable-value sentinels. A resolver never invents consent strings, device identifiers, vendor IDs, impression IDs, or fallback values. dialect_semantic is the cited namespace’s exact meaning. universal_semantic appears only for a verified mapping into AdCP’s closed universal vocabulary. A dialect-defined token such as [PLAYERSTATE] remains specific without being mislabeled as a generic vendor semantic. Unknown tokens are schema-constrained to optional, zero-encoding preservation. Every declaration_id is unique within its asset. Each declared token must exist at the named zero-based occurrence, and every claimed occurrence must have exactly one declaration. This occurrence-level rule prevents one encoding contract from being applied indiscriminately to the same spelling in a URL, path, or opaque markup context.

Capability matching and validation

Sellers publish a seller-wide ceiling in execution.creative_specs.macro_resolution_capabilities. The selected product format option publishes the binding set in its top-level macro_resolution_capabilities field, alongside format_kind and params. Each entry is one exact semantic/operation/actor contract; it does not create a cross-product from a semantic list and maximum depth. Capability metadata is deliberately outside canonical params: it describes who can process a selected format option, not the creative shape itself. Standalone creative agents use a separate route rule: the selected creative.supported_formats[] entry’s format.macro_resolution_capabilities is authoritative for build, preview, and validation, and its build-time entries name performed_by: creative_agent. The sales seller∩product intersection is evaluated later if the resulting manifest enters a seller execution path.
Compatibility matches dialect family, namespace, revision, exact dialect semantic, verified universal mapping when present, operation, actor, occurrence context, and exact encoding. Local capabilities are required only when the local path performs the operation. A token owned by an advertised downstream request executor is classified resolvable; its source bytes remain unchanged until that actor runs. preserved_for_downstream is reserved for an intentional preserve operation with no exact resolver claim. request_executor is functional rather than organizational: it is the component actually issuing the governed VAST/DAAST or tracking request, whether that is a client player, publisher player, SSAI service, or another execution component. validate_input and sync_creatives return one path-addressable macro_resolution_results[] entry per declaration:
  • resolvable — the selected path advertises the exact operation; no serve-time result is implied;
  • preserved_for_downstream — the declaration intentionally requests byte-preserved handoff and makes no exact resolver claim;
  • unsupported — no compatible resolver/capability exists; or
  • ambiguous — the declaration does not establish one safe meaning.
A required unsupported or ambiguous token fails with MACRO_RESOLUTION_FAILED. Its error.details MUST conform to error-details/macro-resolution-failed.json. Results include the asset pointer, declaration ID, requested encoding, structured reason, and advertised encoding candidates.

Relationship to existing macro surfaces

The occurrence contract extends the existing lifecycle; it does not replace it:
  1. format.supported_macros is the legacy named-format projection of accepted AdCP universal semantics. It does not prove native-dialect execution, ownership, or encoding.
  2. build_creative.macro_values and preview macro values supply concrete raw values. With declarations, a value binds only a verified-universal resolve_value occurrence performed_by creative_agent and advertised by the selected creative route, after representation selection, using its exact encoding. It never bypasses translate_to_native or the target capability chain. Without declarations, the legacy behavior remains unchanged for 3.x compatibility.
  3. translateUniversalMacros has a raw native arm and a one-pass encoded value arm. With declarations, a native mapping performs translate_to_native, emits the exact target token unchanged, and replaces the source declaration with its translation_target; a concrete mapping follows the declaration’s encoding. Existing unmapped/frozen-consent diagnostics remain mandatory.
  4. Dropping an unmapped query parameter is behavior of that explicitly invoked legacy helper. It is not the ingest or transport default. Assets without declarations retain opaque-token transport and are not proven compatible.
  5. Webhook supported_macros describes runtime webhook arguments, while preview/build macros are sample values. Neither is a creative-token resolution capability.
  6. The product tracker execution contract remains responsible for whether a seller accepts and initiates each exact buyer tracker/event combination. Its macro checks reuse these exact capability entries; it does not redefine dialect, ownership, or encoding. Passing one check never implies passing the other.
Catalog-item universal semantics retain their existing stricter rule: UTF-8/NFC values receive exactly one RFC 3986 unreserved-set encoding pass. A declaration cannot weaken or add nesting to that governed catalog substitution profile; nested redirect encoding applies only to a separately cited click-redirect mapping.

Available Macros by Format

Different creative contracts support different macros. Read macro support from the selected product, publisher, or creative-capability canonical declaration.

Common Macros (All Formats)

Privacy & Compliance Macros

Critical for regulatory compliance - Use these to respect user privacy choices in your creative logic.
Privacy Warning: {IP_ADDRESS} is considered personal data under GDPR and many privacy regulations. This macro may return an empty string or masked/truncated IP depending on user privacy settings, publisher policies, and regional regulations. Use geo macros ({COUNTRY}, {REGION}, {CITY}) instead when possible.
Example - Privacy-aware tracking:

Device & Environment Macros

Geographic Macros

Identity Macros

Web Context Macros

For web-based inventory:

Placement & Position Macros

Video Content Macros

For video formats with content context:

Video Ad Pod Macros

For video ads in commercial breaks: Note: Video formats also support all IAB VAST 4.x macros like [CACHEBUSTING], [TIMESTAMP], [DOMAIN], [IFA], etc. These work natively in VAST XML.

Audio Content Macros

For audio formats with content context:

Impression Identification

The {IMPRESSION_ID} macro carries a unique identifier for one impression opportunity. It is a general-purpose impression key — buyers, measurement vendors, attribution providers, and verification services all use this kind of identifier to deduplicate impression events, reconcile pixel firings across vendors, join impressions to clicks, and detect retries. Whichever upstream layer mints the value (see hierarchy below) substitutes it into creative tracking URLs, and any downstream consumer that needs to identify the impression uses the same value. Common use cases:
  • Per-impression deduplication. A single impression often fires many pixels (impression, viewability, video quartiles, completion, third-party verification). All of them sharing one {IMPRESSION_ID} lets downstream consumers reconcile “is this the same impression?” without time- or URL-based heuristics.
  • TMP cross-identity dedup. When a TMP impression resolves to multiple user identities, the buyer’s impression tracker writes the same {IMPRESSION_ID} to every identity’s log so distinct-impression counts dedup correctly. See Impression Tracker Implementation.
  • Cross-vendor reconciliation. Advertisers comparing delivery between an ad server, a verification vendor, and a measurement vendor join on {IMPRESSION_ID} rather than building cross-walks.
  • Pixel retry dedup. A pixel firing twice (network retry, page refresh) carries the same {IMPRESSION_ID}, so a server-side dedup pass on the id catches retries without over-counting.
When to include it:
  • Recommended on every impression tracking URL — the macro is small, cheap, and the use cases above all benefit when it is consistently present.
  • Required for TMP context-only impressions — when Identity Match returned no eligibility (or wasn’t called) and no {TMPX} is present on the pixel, {IMPRESSION_ID} is the only available cross-identity dedup key for the buyer’s impression tracker.
Format is implementation choice. The protocol requires uniqueness with sufficient entropy to avoid cross-seller / cross-time collisions; it does not pin a wire format. UUID (any version), ULID, snowflake-style IDs, or any other collision-resistant identifier scheme are all acceptable. Buyers MUST treat the value as an opaque string for dedup purposes — no parsing, no format assumptions. Three valid sources for the value, in priority order:
  1. Publisher-side mint (highest priority): the publisher’s own first-party code mints a fresh identifier per impression opportunity — e.g., server-side, before the ad request reaches the decision layer — and passes it forward so the decision layer can substitute it via {IMPRESSION_ID}. Works for both context-only and identity-bearing impressions.
  2. Decision-layer mint (used when the publisher hasn’t minted): the ad-decision layer (Prebid TMP module, ad server, SSP, or equivalent client) mints the identifier at the context↔identity join and substitutes it via {IMPRESSION_ID}. Also works for both context-only and identity-bearing impressions.
  3. Buyer-side mint at TMPX decode (fallback, identity-bearing only): when neither layer above minted, the buyer’s impression tracker mints the id locally at TMPX decode time per Impression Tracker Implementation. This works only when {TMPX} is present — it cannot cover context-only impressions.
Each layer MUST defer to whatever value an upstream layer already produced — minting a fresh id at a lower layer when a higher layer already supplied one would split logs for the same impression across two ids. In practice this means: if the publisher minted, the decision layer passes it through; if either of those minted, the buyer uses the value from the pixel rather than minting at decode. Relationship to {CACHEBUSTER}. The two are not interchangeable. {CACHEBUSTER} is a low-entropy anti-cache value sufficient to defeat HTTP intermediaries; it is not a globally-unique impression key. Both can appear in the same tracking URL serving different purposes.

TMP Exposure Tracking

The {TMPX} macro carries an encrypted exposure token from the Identity Match response. It contains the user’s resolved identity tokens encrypted via HPKE, enabling the buyer’s impression pixel to log per-user exposures for real-time frequency capping. Publishers substitute {TMPX} into tracking URLs exactly like other macros. The token is opaque — publishers MUST NOT parse, log, or make decisions based on its value. See TMPX Exposure Tokens for the encryption format and key management.

AXE Integration (Legacy)

The {AXEM} macro is from the legacy AXE integration. In TMP, this is replaced by:
  • Structured creative assets move to the creative_manifest field on the Offer.
  • Loose dynamic rendering values use the Offer’s creative_data map under the package’s publisher/creative contract; they are not ad-server macro substitutions.
  • Per-user exposure tracking uses the {TMPX} macro from Identity Match.

Catalog Item Macros

For catalog-driven creatives (carousels, dynamic product ads, job boards, store locators). These macros resolve to the identifier of the specific catalog item being rendered at serve time — the same identifiers used in conversion event content_ids via the content_id_type field. Use the macro that matches your catalog’s content_id_type. For example, a product catalog with content_id_type: "gtin" uses {GTIN} in tracker URLs; a job catalog uses {JOB_ID}.

Catalog content macros

The macros above resolve to the catalog item’s identifier. Catalog content macros resolve to a catalog item’s scalar field values, for catalog-driven creative templates (sponsored_placement / DPA — Meta DPA, Snap Collection, TikTok Shopping). They are the substitute-the-value analog of the ID macros above: same single-brace family, same substitution-safety rules below, just more tokens. Each token maps 1:1 to a documented catalog item field via the existing catalog_field dot-notation vocabulary used by field_bindings — content macros do not introduce a parallel field vocabulary. The five tokens are deliberately small and cross-vertical; vertical-specific deep fields (e.g. star_rating, salary.min) are served by field_bindings scalars, not universal content macros. URL-valued and image-valued fields (e.g. landing_url, image pools) bind via field_bindings to url/asset slots and are not content macros — substituting a whole URL as an entire href would also break the encodeURIComponent-equivalent percent-encoding contract below. Single-brace {MACRO} only. {{double-brace}} is not an AdCP content-macro syntax and MUST NOT be used. Double-brace is reserved as one of the downstream ad-server macro syntaxes (%%...%%, ${...}, [...], {{...}}) that the sales agent MUST neutralize/percent-encode (see the nested-expansion rule below); adopting it for AdCP content macros would relax that guarantee and collide with downstream ad servers that interpret {{...}} natively. Which catalog items render is seller-declared via the fanout_mode enum on the sponsored_placement format (single_item / per_item / multi_item_in_creative) — there is no buyer-side selection field. On ML-optimized DPA surfaces (Meta Advantage+, TikTok Shopping) the platform commonly overrides buyer-authored overlay text, so content macros are a buyer-declared hint the seller MAY honor, not a guaranteed substitution.

Substitution safety (catalog-item macros)

Catalog-item macros are the one macro class where the value originates in buyer-controlled data (the catalog feed) and expands at impression time into publisher-controlled contexts (impression tracker URLs, click tracker URLs, VAST tracking-event URLs, AND landing / clickthrough URLs — the full set of URL substitution targets above). That flow is attacker-adjacent: a catalog value containing &, #, ?, CR/LF, a stray URL fragment, or a Unicode bidi override can break out of the URL context, inject a Host-header via CRLF, or spoof audit-log rendering if substituted raw. The following rules apply to all catalog-item macros listed above — both the ID macros ({CATALOG_ID}, {SKU}, {GTIN}, {OFFERING_ID}, {JOB_ID}, {HOTEL_ID}, {FLIGHT_ID}, {VEHICLE_ID}, {LISTING_ID}, {STORE_ID}, {PROGRAM_ID}, {DESTINATION_ID}) and the content macros ({ITEM_NAME}, {ITEM_DESCRIPTION}, {ITEM_TAGLINE}, {ITEM_PRICE}, {ITEM_PRICE_CURRENCY}):
  • Normalize to Unicode NFC before encoding. Prior to percent-encoding, catalog-item values that are not already in Unicode Normalization Form C (NFC) MUST be normalized to NFC per Unicode Standard Annex #15. Sellers and buyers MAY send catalog values in any normalization form at sync_catalogs ingest (the catalog is stored as-supplied); the normalization to NFC is a step in the substitution pipeline immediately before percent-encoding, not a catalog-ingest requirement. Without this step, two implementations that both satisfy the unreserved-whitelist rule below produce different bytes for the same visual string — café (NFC: U+00E9) and cafe\u0301 (NFD: U+0065 + combining U+0301) encode to caf%C3%A9 vs e%CC%81 respectively. NFC matches web-platform convention (WHATWG URL, HTML5 DOM, W3C Character Model). NFKC / NFKD are not acceptable substitutes — their compatibility folding silently mutates fullwidth/halfwidth variants and other visually-distinct glyphs that legitimately appear in Japanese/Korean retailer catalogs.
  • Percent-encode every octet that is not in the RFC 3986 unreserved set. Sales agents MUST percent-encode the NFC-normalized catalog-item value such that only RFC 3986 unreserved characters (ALPHA / DIGIT / "-" / "." / "_" / "~") remain unescaped before substituting it into a URL context (query string, path segment, or fragment). Non-ASCII octets MUST be percent-encoded after UTF-8 encoding per RFC 3986 §2.5. This is the encodeURIComponent-equivalent contract: reserved characters (: / ? # [ ] @ ! $ & ' ( ) * + , ; =) are escaped as one would expect, but so are CR (%0D), LF (%0A), space (%20), C0/C1 control characters, and Unicode bidi overrides — the broader enumeration closes CRLF-injection and bidi-spoofing vectors that a reserved-only rule would leave open. Encoding is applied exactly once at substitution time; downstream VAST players and ad servers firing the URL verbatim is the expected contract — they do not and MUST NOT re-decode before firing.
  • Nested macro expansion is prohibited. A catalog-item value that itself contains text matching AdCP’s {MACRO_NAME} syntax MUST NOT be re-expanded. Sales agents perform AdCP macro substitution in one pass: source placeholders are replaced with literal values, and those literal values are not re-scanned. A {JOB_ID} value of vacancy-{DEVICE_ID}-42 produces the literal string vacancy-%7BDEVICE_ID%7D-42 (after percent-encoding of the braces) in the emitted URL, not a second-round expansion. This rule binds AdCP’s {...} syntax only; catalog-item values containing downstream ad-server macro syntaxes (%%...%%, ${...}, [...], {{...}}) remain the sales agent’s responsibility to neutralize when targeting an ad server that would interpret them — percent-encoding per the rule above typically suffices, since %, $, [, ], and { all land outside the unreserved set.
  • Scope is URL contexts only. These rules apply when a catalog-item macro is substituted into a URL context. When a catalog-item macro is substituted into an HTML-attribute context (for example, a banner template’s href or data-* attribute rendered server-side), percent-encoding per this section does not by itself prevent attribute-context breakout; the renderer MUST additionally apply HTML-attribute escaping — the two encodings are layered, not alternatives, because the value must survive both the URL parser and the HTML attribute parser. AdCP’s normative contract bounds to the URL-context case; publisher-side HTML-attribute handling is out of scope for this spec.
Non-catalog macros ({MEDIA_BUY_ID}, {PACKAGE_ID}, {CREATIVE_ID}, {COUNTRY}, {DEVICE_TYPE}, etc.) are populated from publisher- or ad-server-mediated state, not from buyer-supplied feed data. Their encoding contract is governed by the ad-server integration (OpenRTB, VAST), which percent-encodes by convention. Some macros in this class derive from attacker-spoofable inputs ({USER_AGENT}, {REFERRER}, {PAGE_URL}, {DOMAIN}, {APP_BUNDLE} come from request headers or page metadata); the OpenRTB / ad-server encoding convention is the control today. This spec’s normative MUST deliberately scopes to the buyer-controlled catalog-item class — a narrower, verifiable contract than a universal canonicalization rule. Conformance fixture. Reference test vectors pinning the encoding behavior — reserved-character breakout, nested-expansion literal preservation, CRLF injection, non-ASCII — are versioned at static/compliance/source/test-vectors/catalog-macro-substitution.json. Sales agents SHOULD validate their substitution code against these vectors before shipping.

Creative Variant Macros

Note: Publisher-specific custom macros may be defined in individual creative format specifications as extra supported macros.

Usage Examples

Video Creative with Tracking

Key Points:
  • Mix AdCP macros ({MEDIA_BUY_ID}) with VAST macros ([CACHEBUSTING])
  • AdCP macros use {CURLY_BRACES}
  • VAST macros use [SQUARE_BRACKETS]
  • Declare dialect, owner, and encoding when compatibility must be proven before trafficking

Display Creative with Tracking

Audio Creative with Tracking

Catalog-Driven Creative with Item Tracking

Key point: {GTIN} resolves to the specific product’s GTIN at serve time. For a carousel showing 5 products, each product impression/click fires with that product’s identifier — enabling per-item attribution.

Macro Availability by Inventory Type

Not all macros are available in all inventory types. Check format specifications to see which macros are supported. This matrix is informative implementation guidance, not a capability claim. The selected product format option’s exact macro_resolution_capabilities entry is authoritative for dialect identity, operation, actor, context, and encoding; legacy named formats may expose only the coarser supported_macros projection. Important: The columns below represent format types (Display, Video, etc.) which can run in different environments (app vs web). For example:
  • Display ads in mobile apps have DEVICE_ID (✅*), but display ads on web do not
  • The ✅* notation means “available in-app contexts only”
  • Format type + inventory environment determine actual macro availability
Legend:
  • ✅ = Available
  • ❌ = Not available
  • ✅* = In-app only (not mobile web)
  • ✅† = When location permission granted
  • ✅‡ = Often restricted due to privacy regulations (may return empty or masked value)
  • ✅§ = DOOH uses play-log-based reporting rather than pixel URLs
Important Notes:
  • Privacy macros ({LIMIT_AD_TRACKING}, {DEVICE_ID}) may return empty values based on user privacy settings
  • Geographic macros accuracy varies by publisher’s data capabilities
  • {PLACEMENT_ID} refers to the IAB Global Placement ID standard

How Macros Work

1. Discovery

Inspect the selected canonical declaration to see which macros it supports:
Legacy named formats may still expose supported_macros as an array of universal macro names. That is a compatibility projection, not proof of actor, native dialect, context, or encoding support.

2. Include Macros in Creatives

Add macro placeholders in your tracking URLs using {MACRO_NAME} syntax:

3. Sales Agent Processing

When you create a media buy via create_media_buy, the sales agent:
  1. Replaces AdCP ID macros with your actual IDs:
    • {MEDIA_BUY_ID}mb_spring_2025
    • {PACKAGE_ID}pkg_ctv_prime
    • {CREATIVE_ID}cr_video_30s
  2. Translates platform macros to their ad server’s syntax:
    • {CACHEBUSTER}%%CACHEBUSTER%% (GAM) or {{timestamp}} (Kevel)
    • {DEVICE_ID}%%ADVERTISING_IDENTIFIER_PLAIN%% (GAM)
    • {DOMAIN}%%SITE%% (GAM)
  3. Inserts click trackers automatically into clickable elements
  4. Leaves VAST macros unchanged (for video formats)

Implementing translation with the SDK

Sales agents don’t have to hand-roll the rewrite. The @adcp/sdk package ships a translateUniversalMacros helper that applies a per-macro mapping to a tracking URL’s query-parameter values. Each macro maps to either a native ad-server token (left raw, filled by the ad server at impression time) or a concrete value the agent already knows (substituted now, percent-encoded per RFC 3986): The behavior below is the established undeclared-token compatibility path. With occurrence declarations, a native mapping implements translate_to_native and emits the target declaration, while a concrete value follows the declaration’s exact encoding profile. The helper’s safety and privacy diagnostics are unchanged in either mode.
Behavior:
  • native entries are inserted verbatim%%CACHEBUSTER%% is not percent-encoded, so the ad server still recognizes it. A native mapping containing U+0000–U+001F or U+007F is invalid: the helper raises a typed unsafe_native_mapping error and emits no translated URL.
  • value entries are RFC-3986 percent-encoded, so a value containing reserved characters can’t break or inject into the URL.
  • A consent/privacy macro supplied through value is still encoded, but it is also surfaced in frozen_consent_macros. Inspect this advisory field: fixing a consent string at translation time can produce a stale-consent pixel.
  • A parameter whose universal macro the agent does not support is dropped entirely (its key is reported in dropped_params, the macro in unmapped_macros) — better to omit a tracker the agent can’t fill than to emit a broken one. A dropped consent/privacy macro ({GDPR_CONSENT}, {US_PRIVACY}, …) is also surfaced in dropped_consent_macrosinspect it so a forgotten mapping doesn’t silently ship a consent-degraded pixel.
  • Already-minted parameters pass through untouchedpkg_id=123456 (no macro) is left exactly as-is.
  • suspect_native_values flags any value entry shaped like a native token (%%…%%, {{…}}, ${…}, [UPPER_SNAKE]) — almost always a mapping that should have used the native arm.
  • Only query-parameter values are translated; a macro in a key position is left untouched. The path and fragment also pass through byte-for-byte.
  • Substitution is single-pass. Macro-shaped text inside a substituted value is data, percent-encoded once, and never re-expanded.
  • A URL ending in a bare trailing ? is normalized by removing the delimiter.
  • Diagnostic ordering is deterministic: dropped_params follows query occurrence order (including repeated keys); unmapped_macros and dropped_consent_macros follow first occurrence and are deduplicated; frozen_consent_macros and suspect_native_values follow mapping property order and are deduplicated.
The language-neutral golden fixture at /compliance/latest/test-vectors/universal-macro-translation.json pins the output URL, every diagnostic array, typed rejection cases, and ratified edge-case ordering. SDK implementations MUST run the applicable versioned fixture rather than maintain an independent behavioral table.

4. Impression Time

The publisher’s ad server replaces remaining macros with actual values:

Best Practices

Use Macros Consistently

Include the same core set of macros across all your creatives:
This makes your tracking data consistent and easier to analyze.

Check Format Support

Always inspect the selected canonical declaration. Not all formats support all macros.

Combine VAST and AdCP Macros

For video, use both systems together:
  • VAST macros [CACHEBUSTING], [TIMESTAMP] - for standard video tracking
  • AdCP macros {MEDIA_BUY_ID}, {DEVICE_ID} - for your campaign tracking

Privacy Compliance

Critical: Always respect user privacy choices in your creative logic.

GDPR Compliance (EU Traffic)

For campaigns serving in the EU:

US Privacy / CCPA Compliance

For US traffic:

Device-Level Privacy

Respect Limit Ad Tracking settings:

Privacy Macro Behavior

Empty Values: Privacy-restricted macros return empty strings or zeros:
  • {DEVICE_ID}"" or 00000000-0000-0000-0000-000000000000 when LAT enabled
  • {GDPR_CONSENT}"" when no consent provided
  • {IP_ADDRESS}"" or masked/truncated IP when privacy restricted
Always test for empty values before using privacy-sensitive macros.

URL Encoding

Do not URL-encode the placeholder token itself. Supply raw values to the actor named by the occurrence declaration; that actor applies the declaration’s exact encoding profile. Depending on the execution path, the actor may be the seller, source ad server, or request executor. Example:
The declared actor encodes the concrete value exactly as specified. Callers must not pre-encode values, and downstream actors must not add an undeclared pass.

Template Syntax

AdCP macro-bearing URLs are not RFC 6570 templates. Plain URLs retain ordinary absolute HTTP(S) URI validation. For structural validation, verifiers mask recognized token lexical forms ({NAME}, ${NAME}, [NAME], and %%NAME%%) with an RFC 3986 unreserved sentinel and validate the resulting absolute HTTP(S) URI. This masking applies whether the occurrence is declared or travels on the undeclared 3.x compatibility path; it assigns no meaning, actor, or encoding and does not change the original bytes. RFC 6570 operators ({+SKU}, {#SKU}, {?SKU}, and similar forms) have no special expansion meaning. Processing is literal token replacement only when an applicable declaration or the explicit legacy helper authorizes it.

Implementation Notes for Sales Agents

This section is for AdCP implementers, not buyers.

Macro Translation Approach

Sales agents must translate universal macros to their ad server’s native syntax. The recommended approach: Option 1: Hard-Code During Trafficking (MVP)
  • When creating ad server creatives, replace AdCP ID macros with actual values
  • Translate platform macros to ad server syntax
  • Creates one creative per line item but is simple and reliable
Option 2: Dynamic Wrapper (Future)
  • Intercept ad calls and inject values dynamically
  • More complex but avoids creative duplication

Translation Examples

Google Ad Manager:
Kevel:
Xandr Monetize:

Click Tracker Insertion

A click on a creative produces two distinct signals, and AdCP models them as separate asset slots: This separation is inherited from IAB OpenRTB Native: a click has exactly one destination and N fire-and-forget click trackers (the link.fallback deeplink is an alternative form of that one destination, not a second one). At serve time the sales agent fires every click_tracker as a count beacon and navigates the user to the one landing_page_url. Both slots are defined in Asset Types. Macro insertion. Sales agents insert their ad server’s click-tracking macro ahead of the destination so the platform records the click and forwards the user. The destination encoding is ad-server-specific (GAM uses append-style %%CLICK_URL_UNESC%%<landing>; other servers ship a self-contained ?...&rurl=<encoded-landing> form): Original creative:
After insertion (GAM):
Preserving the buyer’s clickthrough parameters. Inserting a click macro can strip query parameters already present on the buyer-supplied clickthrough URL — including ones the agent does not recognize, such as a buyer-side vendor’s click identifier — which silently breaks downstream attribution. An agent that forwards these parameters applies the encoding discipline Substitution safety defines for catalog-item values (Unicode NFC normalization, then percent-encoding to the RFC 3986 unreserved set) to any value derived from buyer- or third-party-supplied data, so preservation does not reopen URL-context breakout, CRLF injection, or bidi spoofing. Whether to make parameter preservation a normative requirement is part of the click-tracking proposal under working-group review (#5693). Getting a buyer’s click identifier onto the landing. A click_tracker is a count beacon: it records the click but does not place its identifier into the user’s landing session, because only landing_page_url is navigated. When a buyer needs its click identifier to reach the landing (for click-level conversion attribution), the reliable, redirect-free pattern is to put that identifier directly on the landing_page_url it supplies — either as a literal value (&buyer_click_id=abc123) or composed from an AdCP macro the buyer controls (&buyer_click_id={CREATIVE_ID}, expanded by the sales agent at serve time). {buyer_click_id} is not itself an AdCP universal macro — that universal set is closed even though qualified vendor dialects remain extensible — so the buyer supplies the value, not a new universal token. Because the identifier rides on the one navigated URL, no party’s redirect has to be the terminal hop. This works for any identifier the buyer can supply or derive ahead of serve time, and avoids multi-party redirect chaining entirely. Identifiers that can only be minted at click time (inside a vendor’s own redirect) cannot survive on a count beacon, and routing the user through the vendor’s redirect so its identifier lands requires a chaining contract AdCP does not define. A buyer-declarable “this tracker must be in the navigation path” slot semantic is under working-group discussion (#5693); until it lands, the count beacon is the default and in-path identifier survival is out of scope.

Mapping Storage

Store the mapping between AdCP IDs and ad server IDs for reconciliation:
Return this in create_media_buy responses and make it queryable for reconciliation.