Skip to main content
Conversion tracking in AdCP connects advertising spend to business outcomes. Two tasks handle the lifecycle: sync_event_sources configures where events come from, and log_event sends the events themselves. Event data feeds into delivery reporting (conversions, ROAS, cost per acquisition) and enables optimization goals on media buy packages.

The flow

This shows the recommended order. In practice, media buys can be created before events are flowing — the seller begins optimizing once sufficient event history accumulates.

Event source

An event source represents a channel through which conversion events are collected — a website pixel, mobile SDK, server-to-server integration, or CRM import. Configure event sources with sync_event_sources. You provide an event_source_id, optional name, event_types, and allowed_domains. The response includes additional fields for each source:
FieldTypeDescription
seller_idstringSeller-assigned identifier in their ad platform
actionstringWhat happened: created, updated, unchanged, deleted, failed
managed_bystringbuyer (you configured it) or seller (always-on, seller-managed)
action_sourceActionSourceType of event source (website pixel, app SDK, etc.)
setupobjectImplementation details — snippet code, snippet type, instructions

Buyer-managed vs seller-managed

Buyer-managed sources are ones you configure via sync_event_sources. You control the event types, domains, and lifecycle. Seller-managed sources are always-on and appear in the response with managed_by: "seller". These are common in commerce media where the retailer provides built-in attribution (e.g., purchase tracking on their own platform). Products with conversion_tracking.platform_managed: true indicate the seller provides these sources. To discover all sources on an account (including seller-managed), call sync_event_sources without an event_sources array:
{
  "$schema": "https://adcontextprotocol.org/schemas/v3/media-buy/sync-event-sources-request.json",
  "account": { "account_id": "acct_12345" }
}

Event

An event represents a user action — a purchase, lead submission, page view, app install, or any of the standard event types. Send events with log_event:
{
  "$schema": "https://adcontextprotocol.org/schemas/v3/core/event.json",
  "event_id": "evt_purchase_12345",
  "event_type": "purchase",
  "event_time": "2026-01-15T14:30:00Z",
  "action_source": "website",
  "event_source_url": "https://www.example.com/checkout/confirm",
  "user_match": {
    "hashed_email": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
    "click_id": "abc123def456",
    "click_id_type": "gclid"
  },
  "custom_data": {
    "value": 149.99,
    "currency": "USD",
    "order_id": "order_98765",
    "num_items": 3
  }
}
FieldTypeRequiredDescription
event_idstringYesUnique identifier for deduplication (scoped to event_type + event_source_id). Max 256 chars.
event_typeEventTypeYesStandard event type
event_timedate-timeYesISO 8601 timestamp when the event occurred
user_matchUserMatchNoUser identifiers for attribution matching
custom_dataCustomDataNoEvent-specific data (value, currency, items)
action_sourceActionSourceNoWhere the event occurred
event_source_urluriNoURL where the event occurred (required when action_source is website)
custom_event_namestringNoName for custom events (when event_type is custom)
Events are deduplicated by event_id + event_type + event_source_id. Sending the same event multiple times is safe.

User match

User identifiers enable the seller to attribute conversions to ad impressions. Provide the strongest identifiers available — more identifiers means higher match rates.
{
  "$schema": "https://adcontextprotocol.org/schemas/v3/core/user-match.json",
  "hashed_email": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
  "uids": [
    { "type": "uid2", "value": "AbC123XyZ..." }
  ],
  "click_id": "abc123def456",
  "click_id_type": "gclid"
}
At least one identifier is required. The hierarchy from strongest to weakest:
FieldTypeMatch qualityDescription
uidsUID[]DeterministicUniversal ID values (rampid, id5, uid2, euid, pairid, maid)
hashed_emailstringDeterministicSHA-256 hash of lowercase, trimmed email (64-char hex)
hashed_phonestringDeterministicSHA-256 hash of E.164 phone number (64-char hex)
click_idstringDeterministicPlatform click identifier (fbclid, gclid, ttclid, etc.)
click_id_typestringType of click identifier
client_ipstringProbabilisticClient IP address (requires client_user_agent)
client_user_agentstringProbabilisticClient user agent (requires client_ip)
Hashing: Normalize before hashing — emails to lowercase with whitespace trimmed, phone numbers to E.164 format (e.g., +12065551234). Hash with SHA-256, output as 64-character lowercase hex. Send multiple identifier types when available. The seller uses the best available match.

Custom data

Event-specific data for attribution and reporting. For purchase events, always include value and currency to enable ROAS reporting.
{
  "$schema": "https://adcontextprotocol.org/schemas/v3/core/event-custom-data.json",
  "value": 149.99,
  "currency": "USD",
  "order_id": "order_98765",
  "content_ids": ["SKU-1234", "SKU-5678"],
  "num_items": 3,
  "contents": [
    { "id": "SKU-1234", "quantity": 2, "price": 49.99, "brand": "Acme" },
    { "id": "SKU-5678", "quantity": 1, "price": 50.01, "brand": "Nova" }
  ]
}
FieldTypeDescription
valuenumberMonetary value of the event
currencystringISO 4217 currency code (e.g., USD, EUR, GBP)
order_idstringUnique order or transaction identifier
content_idsstring[]Product or content identifiers
content_typestringCategory of content (product, service, etc.)
content_namestringName of the product or content
content_categorystringCategory of the product or content
num_itemsintegerNumber of items in the event
search_stringstringSearch query (for search events)
contentsContent[]Per-item details: id (required), quantity, price, brand

Event types

Standard marketing event types, aligned with IAB ECAPI:
Event typeDescription
page_viewUser viewed a page
view_contentUser viewed specific content (product, article, etc.)
select_contentUser selected or clicked on content
select_itemUser selected a specific product or item from a list
searchUser performed a search
shareUser shared content via social or messaging
add_to_cartUser added an item to cart
remove_from_cartUser removed an item from cart
viewed_cartUser viewed their shopping cart
add_to_wishlistUser added an item to a wishlist
initiate_checkoutUser started checkout process
add_payment_infoUser added payment information
purchaseUser completed a purchase
refundA purchase was fully or partially refunded (adjusts ROAS)
leadUser expressed interest (form submission, signup, etc.)
qualify_leadLead qualified by sales or scoring criteria
close_convert_leadLead converted to a customer or closed deal
disqualify_leadLead disqualified or marked as not viable
complete_registrationUser completed account registration
subscribeUser subscribed to a service or newsletter
start_trialUser started a free trial
app_installUser installed an application
app_launchUser launched an application
contactUser initiated contact (call, message, etc.)
scheduleUser scheduled an appointment or event
donateUser made a donation
submit_applicationUser submitted an application (loan, job, etc.)
customCustom event type (specify in custom_event_name)

Action sources

Where the conversion event originated:
Action sourceDescription
websiteEvent occurred on a website
appEvent occurred in a mobile or desktop app
offlineEvent occurred offline (imported data)
phone_callEvent originated from a phone call
chatEvent originated from a chat conversation
emailEvent originated from an email interaction
in_storeEvent occurred at a physical retail location
system_generatedEvent generated by an automated system
otherOther source (specify in ext)

Event source health

Sellers that evaluate event source quality include a health object on each source in the sync_event_sources response. This is the AdCP equivalent of platform-specific quality scores like Snap’s Event Quality Score (EQS) or Meta’s Event Match Quality (EMQ). The status field is the AdCP-standardized score — comparable across all sellers:
StatusMeaning
insufficientSetup incomplete or event quality too low — optimization cannot run
minimumFunctional but data quality limits optimization effectiveness
goodMeets quality thresholds for most optimization goals
excellentExceeds quality thresholds across all dimensions
Buyer agents should key decisions off status, not detail. The optional detail object contains seller-specific scoring (e.g., Snap’s 0-10 EQS, Meta’s 0-10 EMQ) for human dashboards or advanced diagnostics, but scales vary by seller and cannot be compared across platforms.
FieldTypeDescription
statusstringAdCP-standardized health level. Use for cross-seller decisions.
detailobjectSeller-specific score, max_score, and optional label. Only present when the seller has a native quality score.
match_ratenumberFraction of events matched to ad interactions (0.0-1.0). Low rates indicate weak user_match identifiers. Only available from sellers that compute match rates (Snap, Meta).
last_event_atdate-timeTimestamp of the most recent event received.
evaluated_atdate-timeWhen this health assessment was computed. Use to detect stale assessments.
events_received_24hintegerEvents received in the last 24 hours.
issuesarrayActionable problems with severity and message. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.
Health is reported per event source, not per account. A buyer with a healthy website pixel and a broken app SDK will see different health on each. When health is absent, the seller does not evaluate event source quality. Buyer agents should proceed without health gating — the seller handles quality internally. Do not treat absent health as insufficient.

How sellers compute health

Sellers with native API-accessible quality scores (Snap EQS, Meta EMQ) relay them directly in status and detail. Most sellers do not have native scores — they derive status from operational metrics:
  • insufficient: tag inactive, or events_received_24h is 0
  • minimum: tag active, low volume or high error rate
  • good: firing steadily, reasonable volume, core event types covered
  • excellent: high volume, low errors, enhanced matching enabled
When sellers compute health from reporting data, the evaluated_at timestamp tells the buyer how fresh the assessment is. Assessments older than 24 hours may not reflect recent changes to tag configuration or event volume. The detail object is absent for these sellers — there is no native score to relay. Schema: /schemas/latest/core/event-source-health.json

Measurement readiness

Products that support event-based optimization can include a measurement_readiness object in get_products responses. This tells the buyer whether their event setup is sufficient for the product to optimize effectively.
FieldTypeDescription
statusstringAdCP-standardized level: insufficient, minimum, good, excellent
required_event_typesEventType[]Event types this product needs
missing_event_typesEventType[]Required types the buyer hasn’t configured
issuesarrayActionable problems with severity and message
notesstringSeller explanation or recommendations
Measurement readiness is evaluated per product in the context of the buyer’s account. The same product shows different readiness for different buyers depending on their event source configuration. When measurement_readiness is absent, the product either does not use event-based optimization (CTV awareness, guaranteed display) or the seller does not provide readiness assessments. In both cases, the buyer agent should treat the product as viable. Do not treat absent readiness as insufficient. Unlike event source health, measurement readiness has no evaluated_at timestamp — it is evaluated fresh on each get_products call using the buyer’s current event source configuration.

Cross-seller buyer agent pattern

A buyer agent talking to multiple sellers writes one set of rules that works everywhere. Any status other than insufficient means the product can optimize — the question is how well. The standardized status field means no per-seller integration code:
test=false
// Works across all sellers — no seller-specific logic
for (const seller of sellers) {
  const sources = await seller.syncEventSources({ account: seller.account });

  // Surface issues from any seller — sort by severity, don't rely on array position
  for (const source of sources.event_sources) {
    if (source.health?.status === "insufficient") {
      surfaceIssues(source.health.issues ?? []);
    }
  }

  const products = await seller.getProducts({
    account: seller.account,
    buying_mode: "brief",
    brief: campaign.brief,
  });

  for (const product of products.products) {
    const mr = product.measurement_readiness;

    // Absent = no event-based optimization needed (CTV, awareness), treat as viable
    if (!mr) {
      viable.push(product);
      continue;
    }

    // For DR products, require good or better
    if (campaign.goal === "conversions" && mr.status === "minimum") {
      warnings.push({ product, reason: "Event setup is functional but limits optimization" });
      viable.push(product); // Still viable, but flag it
    } else if (mr.status !== "insufficient") {
      viable.push(product);
    } else {
      skipped.push({ product, issues: mr.issues });
    }
  }
}
Schema: /schemas/latest/core/measurement-readiness.json

Trust boundaries

The issues[].message, measurement_readiness.notes, and detail.label fields are seller-provided free text. Buyer agents should treat these as untrusted content — do not pass them directly into LLM system prompts or use them as decision-making inputs without a trust boundary. They are safe to display to humans or include in informational context, but should not influence agent control flow.

Optimization goals

Optimization goals tell the seller what to optimize delivery toward. Set them on a package in create_media_buy. A package accepts an array of goals — each with an optional priority (1 = highest). Products declare max_optimization_goals when they limit how many goals a package can carry (most social platforms accept only 1). Schema: /schemas/v3/core/optimization-goal.json There are two kinds of goals, discriminated by kind:
  • kind: "metric" — Optimize for a seller-tracked delivery metric (clicks, views, engagements, etc.). No event source or conversion tracking setup required. The product declares which metrics it supports in metric_optimization.
  • kind: "event" — Optimize for advertiser-tracked conversion events. Requires event sources registered via sync_event_sources. The product declares support in conversion_tracking.

kind: event

Optimize for advertiser-tracked conversion events. The event_sources array defines which source-type pairs feed this goal. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event reported by multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is absent or false, buyers should use a single event source per goal. Cost per conversion (single source):
{
  "kind": "event",
  "event_sources": [
    { "event_source_id": "website_pixel", "event_type": "lead" }
  ],
  "target": { "kind": "cost_per", "value": 25.00 },
  "priority": 1
}
Return on ad spend (multiple sources with refunds):
{
  "kind": "event",
  "event_sources": [
    { "event_source_id": "web_pixel", "event_type": "purchase", "value_field": "order_total" },
    { "event_source_id": "app_sdk", "event_type": "purchase", "value_field": "order_total" },
    { "event_source_id": "web_pixel", "event_type": "refund", "value_field": "refund_amount", "value_factor": -1 }
  ],
  "target": { "kind": "per_ad_spend", "value": 4.0 },
  "attribution_window": { "post_click": { "interval": 28, "unit": "days" }, "post_view": { "interval": 1, "unit": "days" } },
  "priority": 1
}
For per_ad_spend targets, each event source entry specifies a value_field (which field on custom_data carries the monetary value) and an optional value_factor (multiplier, defaults to 1). The seller computes sum(value_field * value_factor) / spend across all deduplicated events. Maximize conversion value (no specific ROAS target):
{
  "kind": "event",
  "event_sources": [
    { "event_source_id": "web_pixel", "event_type": "purchase", "value_field": "value" }
  ],
  "target": { "kind": "maximize_value" },
  "priority": 1
}
A maximize_value target steers spend toward higher-value conversions without committing to a specific return ratio. Requires value_field on at least one event source entry.
FieldTypeRequiredDescription
kind"event"YesDiscriminator
event_sourcesarrayYesSource-type pairs feeding this goal. Seller deduplicates by event_id across entries — when the same event_id arrives from multiple sources with different value_fields, the seller uses the value_field and value_factor from the first matching entry in this array.
event_sources[].event_source_idstringYesEvent source (must be configured via sync_event_sources)
event_sources[].event_typeEventTypeYesEvent type to include (e.g., purchase, lead, refund)
event_sources[].custom_event_namestringWhen event_type is customPlatform-specific custom event name
event_sources[].value_fieldstringWhen target is per_ad_spend or maximize_valueWhich field on custom_data carries the monetary value. The seller must use this for value extraction and aggregation — it is not passed directly to underlying platform APIs.
event_sources[].value_factornumberNoMultiplier the seller must apply to value_field before aggregation (default 1). Use -1 for refunds, 0.01 for cents, 0 to zero out a source’s value contribution while still counting it for dedup.
target.kind"cost_per" | "per_ad_spend" | "maximize_value"NoTarget type. When omitted, the seller maximizes conversion count within budget (see default behavior).
target.valuenumberYes (if target set)Cost per event in buy currency, or return ratio (e.g., 4.0 = $4 per $1 spent)
attribution_windowobjectNoClick-through and view-through windows. When omitted, the seller uses their default.
priorityintegerNo1 = highest priority. When omitted, sellers use array position.

kind: metric

Optimize for a seller-tracked delivery metric. No event source needed — the seller tracks these natively. Products declare which metrics they support in metric_optimization.supported_metrics. Maximize clicks (no target — seller optimizes for volume within budget):
{
  "kind": "metric",
  "metric": "clicks"
}
Cost per click:
{
  "kind": "metric",
  "metric": "clicks",
  "target": { "kind": "cost_per", "value": 2.00 },
  "priority": 2
}
Minimum click-through rate:
{
  "kind": "metric",
  "metric": "clicks",
  "target": { "kind": "threshold_rate", "value": 0.001 },
  "priority": 2
}
Minimum attention time:
{
  "kind": "metric",
  "metric": "attention_seconds",
  "target": { "kind": "threshold_rate", "value": 5.0 },
  "priority": 3
}
Maximize engagements (social reactions, comments, shares, story opens, overlay taps):
{
  "kind": "metric",
  "metric": "engagements"
}
Completed views with duration threshold (6-second views on TikTok):
{
  "kind": "metric",
  "metric": "completed_views",
  "view_duration_seconds": 6,
  "target": { "kind": "cost_per", "value": 0.02 },
  "priority": 1
}
FieldTypeRequiredDescription
kind"metric"YesDiscriminator
metricstringYesSeller-native metric (see metrics table below)
view_duration_secondsnumberNoMinimum video view duration (in seconds) that qualifies as a completed_views event. Only applicable when metric is completed_views. When omitted, the seller uses their platform default. Must be a value listed in the product’s metric_optimization.supported_view_durations — sellers reject unsupported values.
target.kind"cost_per" | "threshold_rate"NoTarget type. When omitted, the seller maximizes metric volume within budget.
target.valuenumberYes (if target set)Cost per metric unit in buy currency, or minimum per-impression value
priorityintegerNo1 = highest priority. When omitted, sellers use array position.
Metrics:
MetricUnitthreshold_rate exampleDescription
clickscount/impression0.001 (0.1% CTR)Link clicks, swipe-throughs, CTA taps that navigate away
viewscount/impression0.70 (70% viewability)Viewable impressions
completed_viewscount/impression0.85 (85% VCR)Video or audio completions. Use view_duration_seconds to control the qualifying threshold (e.g., 2s, 6s, 15s).
viewed_secondsseconds/impression3.0 (3s in view)Time in view per impression
attention_secondsseconds/impression5.0 (5s attention)Attention time per impression
attention_scorescore/impression40.0 (vendor-specific)Attention score per impression
engagementscount/impressionDirect interaction beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio
followscount/impressionNew followers, page likes, artist/podcast/channel subscribes
savescount/impressionSaves, bookmarks, playlist adds, pins — signals of intent to return
profile_visitscount/impressionVisits to the brand’s in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks (use clicks for that).
reachunique entities/windowUnique audience reach within a frequency window. Requires reach_unit (e.g., households, individuals). Use target_frequency to set the frequency band for optimization.

Target kinds

All target kinds across both goal types:
target.kindMetric goalsEvent goalsDescription
cost_perCost per click/view/etc.Cost per conversion eventspend / count
threshold_rateMinimum per-impression valueat least X per impression
per_ad_spendTarget return on ad spendsum(value_field * value_factor) / spend
maximize_valueMaximize total conversion valueSteers spend toward higher-value conversions. Requires value_field.

Choosing a strategy

GoalWhen to useWhat you set
Max conversionsAs many conversions as possible within budgetkind: "event" + event sources, no target. value_field may be present for reporting but does not change the objective.
Target cost per conversionSpecific cost per eventkind: "event" + target: { kind: "cost_per", value: 25.0 }
Target return on ad spendSpecific return ratio on event valueskind: "event" + value_field on sources + target: { kind: "per_ad_spend", value: 4.0 }
Maximize conversion valueSteer toward higher-value conversions without a ROAS targetkind: "event" + value_field on sources + target: { kind: "maximize_value" }
Max clicksMaximize clicks within budgetkind: "metric", metric: "clicks", no target
Target cost per clickSpecific cost per clickkind: "metric", metric: "clicks" + target: { kind: "cost_per", value: 2.0 }
Target CTRMinimum click-through ratekind: "metric", metric: "clicks" + target: { kind: "threshold_rate", value: 0.001 }
Target viewabilityMinimum viewability ratekind: "metric", metric: "views" + target: { kind: "threshold_rate", value: 0.70 }
Target attentionMinimum attention timekind: "metric", metric: "attention_seconds" + target: { kind: "threshold_rate", value: 5.0 }
Target VCRMinimum video completion ratekind: "metric", metric: "completed_views" + target: { kind: "threshold_rate", value: 0.85 }
Completed views with durationVideo views with specific duration thresholdkind: "metric", metric: "completed_views" + view_duration_seconds: 6
Max engagementsMaximize social interactions within budgetkind: "metric", metric: "engagements", no target
Max followsMaximize new followers/subscriberskind: "metric", metric: "follows", no target
Max savesMaximize saves/bookmarks/playlist addskind: "metric", metric: "saves", no target
Max profile visitsDrive traffic to brand page/profilekind: "metric", metric: "profile_visits", no target
Max unique reachMaximize unique audience within budgetkind: "metric", metric: "reach" + reach_unit: "households", no target
Reach with frequencyReach at 1-3x/week frequency bandkind: "metric", metric: "reach" + reach_unit + target_frequency: { min: 1, max: 3, window: "7d" }

Multiple goals and priority

A package can have multiple goals. Priority controls which the seller treats as primary. A common pattern is to use metric goals as proxy signals when event data is sparse:
"optimization_goals": [
  {
    "kind": "metric",
    "metric": "clicks",
    "target": { "kind": "cost_per", "value": 2.00 },
    "priority": 2
  },
  {
    "kind": "event",
    "event_sources": [
      { "event_source_id": "mobile_sdk", "event_type": "app_install" },
      { "event_source_id": "mmp_adjust", "event_type": "app_install" }
    ],
    "target": { "kind": "cost_per", "value": 10.00 },
    "priority": 1
  }
]
The seller focuses on the priority: 1 goal (installs at $10 cost per, deduplicated across SDK and MMP) and uses clicks as a proxy signal until install data accumulates.

Default behavior for event goals

When target is omitted from an event goal, the seller maximizes conversion count within budget. This is true regardless of whether value_field is present on event sources — value_field without an explicit value-oriented target enables reporting (conversion_value, ROAS in delivery reports) but does not change the optimization objective.
targetvalue_fieldSeller behavior
omittedomittedMaximize event count within budget
omittedpresentMaximize event count within budget. Value available for reporting only.
cost_pereitherTarget cost per conversion. Value used for reporting if present.
per_ad_spendpresentTarget return on ad spend.
per_ad_spendmissingValidation error — seller must reject. No value dimension to compute return.
maximize_valuepresentSteer toward higher-value conversions.
maximize_valuemissingValidation error — seller must reject. No value dimension to maximize.

Blending vs. sequencing goals

value_factor and priority both express “event type A matters more than event type B” but mean different things to the seller’s optimization:
  • value_factor blends multiple event sources into a single objective function. Set per event source entry within a single goal’s event_sources array. The seller sees one goal with a composite value signal. Use this when purchases and page views should be optimized together with explicit relative weights.
  • priority sequences independent goals. Set on separate goal objects in the optimization_goals array. The seller optimizes for goal 1 first; goal 2 is a secondary objective, not blended in. Use this when goals are conceptually separate (e.g., hit a CPA target first, then maximize reach with remaining budget).
Use value_factor to blend. Use priority to sequence. Mixing them up produces subtly wrong optimization — a blended goal that should be sequenced, or sequenced goals that should be blended — and the effect is hard to detect in delivery reports.

Event type polarity

Most event types are positive signals — a purchase, lead, or install is something the buyer wants more of. Some event types are observation signals that should not be standalone optimization targets:
PolarityEvent typesNotes
Positivepurchase, lead, qualify_lead, close_convert_lead, app_install, complete_registration, subscribe, start_trial, contact, schedule, donate, submit_applicationSafe as standalone optimization targets
Upper funnelpage_view, view_content, select_content, select_item, search, add_to_cart, viewed_cart, add_to_wishlist, initiate_checkout, add_payment_info, share, app_launchValid optimization targets but typically used as proxy signals (priority: 2) when lower-funnel data is sparse
Observationrefund, remove_from_cart, disqualify_leadInclude in event_sources for attribution accuracy and ROAS adjustment, not as standalone optimization targets
custom events are not classified here — their polarity depends on the buyer’s definition. Buyer agents should apply the same reasoning when choosing whether a custom event is safe as a standalone target. Observation events are useful inside a composite goal — refund with value_factor: -1 adjusts ROAS downward, which is exactly what you want. The risk is a misconfigured buyer agent that creates a standalone goal optimizing toward refund or remove_from_cart count. This is a buyer-agent implementation concern, not a protocol constraint — the protocol intentionally does not restrict which event types can be optimization targets.

Volume normalization with value_factor

When combining event sources at different volume scales (e.g., page_view in the tens of thousands vs. purchase in the hundreds), the aggregate value in sum(value_field * value_factor) / spend will be dominated by the highest-volume type without explicit weighting. Buyers should use value_factor to express relative weights across sources:
{
  "kind": "event",
  "event_sources": [
    { "event_source_id": "web_pixel", "event_type": "page_view", "value_field": "value", "value_factor": 0.01 },
    { "event_source_id": "web_pixel", "event_type": "purchase", "value_field": "value", "value_factor": 1 }
  ],
  "target": { "kind": "per_ad_spend", "value": 4.0 }
}
Here page_view contributes 1% of its face value, preventing it from dominating the ROAS calculation despite being ~100x more frequent than purchase. Automatic normalization is intentionally out of scope — it requires event history the seller may not have, and would make the ROAS formula opaque. Buyer agents that want to normalize across event types should do so on their end before setting value_factor.

Pricing model vs. optimization goal

The pricing model (CPC, CPM, CPA, etc.) determines what the buyer pays. The optimization goal determines how the seller allocates impressions. These are independent — a package can use CPM pricing while optimizing toward a CPA target, or use CPA pricing while optimizing for ROAS. See Pricing Models for details on billing.

Reach and frequency

Reach-based optimization uses metric: "reach" with two additional fields:
  • reach_unit (required): The unit of measurement — must be a value declared in the product’s metric_optimization.supported_reach_units (e.g., households, individuals).
  • target_frequency (optional): Frequency band that guides optimization. The seller treats impressions toward unreached entities as higher-value and impressions toward already-saturated entities as lower-value. Includes min, max, and window (e.g., "7d", "campaign"). When omitted, the seller maximizes unique reach.
{
  "kind": "metric",
  "metric": "reach",
  "reach_unit": "households",
  "target_frequency": { "min": 1, "max": 3, "window": "7d" },
  "priority": 1
}
For GRP-based buys, use CPP pricing. For hard frequency limits independent of optimization, use frequency_cap on the package. Reach and frequency metrics are available in delivery reporting via get_media_buy_delivery.

Prerequisites

For metric goals (kind: "metric"):
  1. Check product support — The product must declare metric_optimization with the desired metric in supported_metrics. No event source or conversion tracking setup is required.
  2. Check target support — If setting a target, verify the target kind is listed in metric_optimization.supported_targets.
  3. Check view durations — If using completed_views with view_duration_seconds, verify the value is listed in metric_optimization.supported_view_durations.
For event goals (kind: "event"):
  1. Configure event sources — Call sync_event_sources to set up the event sources referenced in event_sources.
  2. Check product support — The product must declare conversion_tracking with the desired target kind in supported_targets.
  3. Check dedup support — If using multiple event sources per goal, verify the seller supports multi_source_event_dedup in get_adcp_capabilities. When unsupported, use a single event source per goal.
  4. Send events — Use log_event to send conversion data. The seller needs event history to optimize effectively.

Attribution windows

Attribution windows control how far back the seller looks to credit an ad impression for a conversion. Common options:
WindowMeaning
post_click: {interval: 7, unit: "days"}Conversions within 7 days of a click
post_click: {interval: 28, unit: "days"}Conversions within 28 days of a click
post_view: {interval: 1, unit: "days"}Conversions within 1 day of viewing an ad
post_view: {interval: 7, unit: "days"}Conversions within 7 days of viewing an ad
Values must match an option in the seller’s conversion_tracking.attribution_windows capability. When omitted, the seller applies their default window.

Connection to delivery reporting

Once event sources are configured and events are flowing, conversion metrics appear in get_media_buy_delivery responses:
  • conversions — Post-click or post-view conversions attributed to the campaign
  • conversion_value — Monetary value of attributed conversions
  • roas — Return on ad spend (conversion_value / spend)
  • cost_per_acquisition — Cost per conversion (spend / conversions)
These metrics are reported per-package when the package has optimization_goals set. Sellers that support by_action_source breakdowns can show conversions split by source (website, app, in_store, etc.).

Catalog-item attribution

For catalog-driven packages, conversion events carry content_ids that identify which catalog items were involved. The catalog’s content_id_type declares what identifier type to expect (sku, gtin, job_id, etc.). Attribution is broad by design: a user might click on one item (job A) but convert on another (apply to job B). The event fires with the actual content_id of the conversion, not the clicked item. Per-item click-to-conversion path analysis is a platform optimization concern, not a protocol concern. The by_catalog_item breakdown in get_media_buy_delivery shows per-item metrics (impressions, spend, clicks, conversions).