Skip to main content

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.

Send conversion or marketing events for attribution and optimization. Supports batch submissions, test events, and partial failure reporting. Response Time: ~1s (events are queued for processing) Request Schema: /schemas/v3/media-buy/log-event-request.json Response Schema: /schemas/v3/media-buy/log-event-response.json

Quick Start

Log a purchase event:
import { testAgent } from "@adcp/sdk/testing";
import { LogEventResponseSchema } from "@adcp/sdk";

const result = await testAgent.logEvent({
  event_source_id: "website_pixel",
  events: [
    {
      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: {
        click_id: "abc123def456",
        click_id_type: "gclid",
      },
      custom_data: {
        value: 149.99,
        currency: "USD",
        order_id: "order_98765",
        num_items: 3,
      },
    },
  ],
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

// Validate response against schema
const validated = LogEventResponseSchema.parse(result.data);

// Check for operation-level errors first (discriminated union)
if ("errors" in validated && validated.errors) {
  throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
}

if ("events_received" in validated) {
  console.log(`Received: ${validated.events_received}, Processed: ${validated.events_processed}`);
  if (validated.match_quality !== undefined) {
    console.log(`Match quality: ${(validated.match_quality * 100).toFixed(0)}%`);
  }
}

Request Parameters

ParameterTypeRequiredDescription
event_source_idstringYesEvent source configured on the account via sync_event_sources
eventsEvent[]YesEvents to log (min 1, max 10,000)
test_event_codestringNoTest event code for validation without affecting production data

Event Object

FieldTypeRequiredDescription
event_idstringYesUnique identifier for deduplication (scoped to event_type + event_source_id). Max 256 chars.
event_typeEventTypeYesStandard event type (e.g. purchase, lead, add_to_cart)
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 (website, app, in_store, etc.)
event_source_urluriNoURL where the event occurred (required when action_source is website)
custom_event_namestringNoName for custom events (used when event_type is custom)

User Match Object

At least one of uids, hashed_email, hashed_phone, click_id, or client_ip + client_user_agent is required:
FieldTypeDescription
uidsUID[]Universal ID values (rampid, id5, uid2, euid, pairid, maid)
hashed_emailstringSHA-256 hash of lowercase, trimmed email address (64-char hex)
hashed_phonestringSHA-256 hash of E.164-formatted phone number (64-char hex)
click_idstringPlatform click identifier (fbclid, gclid, ttclid, etc.)
click_id_typestringType of click identifier
client_ipstringClient IP address for probabilistic matching
client_user_agentstringClient user agent string for probabilistic matching
Hashing: Hashed identifiers must be SHA-256 hex strings (64 characters, lowercase). Normalize before hashing: emails to lowercase with whitespace trimmed, phone numbers to E.164 format (e.g. +12065551234).

Custom Data Object

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. For catalog-driven campaigns, these match the catalog’s content_id_type (e.g., SKUs, GTINs, job IDs). See catalog-item attribution.
content_typestringCategory of content (product, service, etc.)
num_itemsintegerNumber of items in the event
contentsContent[]Per-item details (id, quantity, price, brand)

Response

Success Response:
  • events_received - Number of events received
  • events_processed - Number of events successfully queued
  • partial_failures - Events that failed validation (with event_id, code, message)
  • warnings - Non-fatal issues (low match quality, missing fields)
  • match_quality - Overall match quality score (0.0 to 1.0)
Error Response:
  • errors - Array of operation-level errors (invalid event source, auth failure)
Note: Responses use discriminated unions - you get either success fields OR errors, never both. Partial failures are reported per-event within the success response.

Common Scenarios

Batch Events

Send multiple events in a single request:
import { testAgent } from "@adcp/sdk/testing";
import { LogEventResponseSchema } from "@adcp/sdk";

const result = await testAgent.logEvent({
  event_source_id: "website_pixel",
  events: [
    {
      event_id: "evt_purchase_001",
      event_type: "purchase",
      event_time: "2026-01-15T10:00:00Z",
      action_source: "website",
      user_match: {
        hashed_email: "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
        uids: [{ type: "uid2", value: "AbC123XyZ..." }],
      },
      custom_data: {
        value: 89.99,
        currency: "USD",
        order_id: "order_001",
      },
    },
    {
      event_id: "evt_lead_002",
      event_type: "lead",
      event_time: "2026-01-15T11:30:00Z",
      action_source: "website",
      user_match: {
        hashed_email: "f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5",
        click_id: "abc123def456",
        click_id_type: "fbclid",
      },
    },
    {
      event_id: "evt_cart_003",
      event_type: "add_to_cart",
      event_time: "2026-01-15T12:15:00Z",
      action_source: "app",
      user_match: {
        uids: [{ type: "rampid", value: "Def456Ghi..." }],
      },
      custom_data: {
        content_ids: ["SKU-1234", "SKU-5678"],
        num_items: 2,
        value: 45.00,
        currency: "USD",
      },
    },
  ],
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = LogEventResponseSchema.parse(result.data);

if ("errors" in validated && validated.errors) {
  throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
}

if ("events_received" in validated) {
  console.log(`${validated.events_processed}/${validated.events_received} events processed`);
  if (validated.partial_failures?.length) {
    for (const failure of validated.partial_failures) {
      console.log(`  Failed: ${failure.event_id} - ${failure.message}`);
    }
  }
}

Test Events

Validate event integration without affecting production data:
import { testAgent } from "@adcp/sdk/testing";
import { LogEventResponseSchema } from "@adcp/sdk";

const result = await testAgent.logEvent({
  event_source_id: "website_pixel",
  test_event_code: "TEST_12345",
  events: [
    {
      event_id: "test_evt_001",
      event_type: "purchase",
      event_time: new Date().toISOString(),
      action_source: "website",
      event_source_url: "https://www.example.com/checkout",
      user_match: {
        click_id: "test_click_abc",
        click_id_type: "gclid",
      },
      custom_data: {
        value: 99.99,
        currency: "USD",
      },
    },
  ],
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = LogEventResponseSchema.parse(result.data);

if ("errors" in validated && validated.errors) {
  throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
}

if ("events_received" in validated) {
  console.log("Test event sent successfully");
  if (validated.warnings?.length) {
    console.log("Warnings:", validated.warnings);
  }
}
Test events appear in the seller’s test events UI but do not affect production attribution or reporting.

In-Store Conversions

Report offline conversions using CRM data:
import { testAgent } from "@adcp/sdk/testing";
import { LogEventResponseSchema } from "@adcp/sdk";

const result = await testAgent.logEvent({
  event_source_id: "crm_import",
  events: [
    {
      event_id: "store_txn_20260115_001",
      event_type: "purchase",
      event_time: "2026-01-15T16:45:00Z",
      action_source: "in_store",
      user_match: {
        uids: [{ type: "rampid", value: "XyZ789AbC..." }],
      },
      custom_data: {
        value: 250.0,
        currency: "USD",
        order_id: "POS-2026-0115-001",
        contents: [
          { id: "SKU-JACKET-L", quantity: 1, price: 189.0 },
          { id: "SKU-SCARF-01", quantity: 1, price: 61.0 },
        ],
      },
    },
  ],
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = LogEventResponseSchema.parse(result.data);

if ("errors" in validated && validated.errors) {
  throw new Error(`Operation failed: ${JSON.stringify(validated.errors)}`);
}

if ("events_received" in validated) {
  console.log(`In-store events processed: ${validated.events_processed}`);
}

Event Deduplication

Events are deduplicated by the combination of event_id + event_type + event_source_id. Sending the same event multiple times is safe - duplicates are silently ignored. Choose event_id values that are stable across retries:
  • Transaction IDs: "order_98765"
  • Composite keys: "purchase_user123_20260115"
  • UUIDs: "550e8400-e29b-41d4-a716-446655440000"

Error Handling

Error CodeDescriptionResolution
REFERENCE_NOT_FOUNDEvent source not configured or not accessible (error.field = event_source_id)Run sync_event_sources first
INVALID_EVENT_TYPEUnrecognized or disallowed event typeCheck event source’s event_types configuration
INVALID_EVENT_TIMEEvent time too far in the past/futureUse timestamps within the seller’s attribution window
MISSING_USER_MATCHNo user identifiers providedInclude at least one of: uids, hashed_email, hashed_phone, click_id, or client_ip + client_user_agent
BATCH_TOO_LARGEMore than 10,000 eventsSplit into smaller batches
RATE_LIMITEDToo many requestsWait and retry with exponential backoff

Best Practices

  1. Configure sources first - Always run sync_event_sources before sending events. Events sent to unconfigured sources are rejected.
  2. Include user_match - Events without user identifiers cannot be attributed. Provide the strongest identifiers available: hashed email/phone > UIDs > click IDs > IP/UA. Send multiple identifier types when available to maximize match rates.
  3. Use test events first - Set test_event_code during integration to validate events appear correctly without affecting production data.
  4. Batch when possible - Send up to 10,000 events per request to reduce API calls. Events within a batch are processed independently.
  5. Include value and currency - For purchase events, always include custom_data.value and custom_data.currency to enable ROAS reporting and optimization.
  6. Stable event IDs - Use deterministic event IDs (order numbers, transaction IDs) rather than random UUIDs. This ensures safe retries without duplicate counting.
  7. Send events promptly - Log events as close to real-time as possible. Events outside the seller’s attribution window may not be matched.

Next Steps