> ## 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.

# sync_event_sources

> sync_event_sources task — configure website pixels, mobile SDKs, server-to-server integrations, and seller- or platform-native event sources on AdCP seller accounts for conversion tracking and attribution.

Configure event sources on a seller account for conversion tracking. Sources can be buyer-managed integrations such as website pixels, mobile SDKs, server-to-server feeds, and CRM imports, or seller- and platform-native owned properties such as channels, profiles, feeds, podcasts, and newsletter lists. Supports upsert semantics, seller-managed sources, and setup instructions.

**Response Time**: \~1s (synchronous configuration)

**Request Schema**: [`/schemas/3.3.0-beta.0/media-buy/sync-event-sources-request.json`](https://adcontextprotocol.org/schemas/3.3.0-beta.0/media-buy/sync-event-sources-request.json)
**Response Schema**: [`/schemas/3.3.0-beta.0/media-buy/sync-event-sources-response.json`](https://adcontextprotocol.org/schemas/3.3.0-beta.0/media-buy/sync-event-sources-response.json)

## Quick Start

Configure an event source for purchase tracking:

```javascript JavaScript theme={null}
import { testAgent } from "@adcp/sdk/testing";
import { SyncEventSourcesResponseSchema } from "@adcp/sdk";

const result = await testAgent.syncEventSources({
  account: { account_id: "acct_12345" },
  event_sources: [
    {
      event_source_id: "website_pixel",
      name: "Main Website Pixel",
      event_types: ["purchase", "lead", "add_to_cart"],
      allowed_domains: ["www.example.com", "shop.example.com"],
    },
  ],
});

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

// Validate response against schema
const validated = SyncEventSourcesResponseSchema.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 ("event_sources" in validated) {
  for (const source of validated.event_sources) {
    console.log(`${source.event_source_id}: ${source.action}`);
    if (source.setup?.snippet) {
      console.log(`  Install: ${source.setup.snippet_type}`);
    }
  }
}
```

```python Python theme={null}
import asyncio
from adcp.testing import test_agent

async def main():
    result = await test_agent.simple.sync_event_sources(
        account={'account_id': 'acct_12345'},
        event_sources=[{
            'event_source_id': 'website_pixel',
            'name': 'Main Website Pixel',
            'event_types': ['purchase', 'lead', 'add_to_cart'],
            'allowed_domains': ['www.example.com', 'shop.example.com']
        }]
    )

    # Check for operation-level errors first
    if hasattr(result, 'errors') and result.errors:
        raise Exception(f"Operation failed: {result.errors}")

    for source in result.event_sources:
        print(f"{source.event_source_id}: {source.action}")
        if source.setup and source.setup.snippet:
            print(f"  Install: {source.setup.snippet_type}")

asyncio.run(main())
```

## Request Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `account` | [account-ref](/dist/docs/3.3.0-beta.0/building/by-layer/L2/accounts-and-agents#account-references) | Yes | Account reference. Pass `{ "account_id": "..." }` or `{ "brand": {...}, "operator": "..." }` if the seller supports implicit resolution. |
| `event_sources` | [EventSource](/dist/docs/3.3.0-beta.0/media-buy/conversion-tracking/#event-source)\[] | No | Event sources to sync. When omitted, the call is discovery-only and returns all existing event sources without modification. |
| `delete_missing` | boolean | No | When true, buyer-managed event sources on the account not in this request are removed (default: false) |

### Event Source Object

| Field | Type | Required | Description |
| - | - | - | - |
| `event_source_id` | string | Yes | Unique identifier for this event source |
| `name` | string | No | Human-readable name |
| `event_types` | [EventType](/dist/docs/3.3.0-beta.0/media-buy/conversion-tracking/#event-types)\[] | No | Event types this source handles. If omitted, accepts all event types. |
| `value_currencies` | string\[] | No | ISO 4217 currencies accepted for monetary event values. Required before using this source for canonical ROAS. One source may support multiple currencies; each buy uses only records matching its media-buy currency. |
| `action_source` | [ActionSource](/dist/docs/3.3.0-beta.0/media-buy/conversion-tracking/#action-sources) | No | Flat source category for compatibility (`website`, `app`, `system_generated`, etc.). Pair with `surface` when the flat value is too coarse. |
| `allowed_domains` | string\[] | No | Domains authorized to send events for this source |
| `surface` | [EventSurface](/dist/docs/3.3.0-beta.0/media-buy/conversion-tracking/#event-surfaces) | No | Structured surface this source represents, such as an owned channel, profile, feed, podcast, newsletter list, website, app, or store |
| `archived` | boolean | No | `true` archives the source, `false` undoes the archive. When present it is the item's only change: every field except `event_source_id` is ignored. Requires `media_buy.event_source_archive`. See [Archiving an event source](#archiving-an-event-source). |

## Response

**Success Response:**

* `event_sources` - Results for each event source, including both synced and seller-managed sources on the account

**Error Response:**

* `errors` - Array of operation-level errors (auth failure, account not found)

**Note:** Responses use discriminated unions - you get either success fields OR errors, never both.

**Each event source in success response includes:**

* All request fields
* `platform_id` - ID assigned to this event source by the seller's ad platform (optional; new in AdCP 3.3)
* `seller_id` - Deprecated compatibility alias for `platform_id`
* `action` - What happened: `created`, `updated`, `unchanged`, `deleted`, `archived`, `failed`
* `archived` - `true` when the source is archived. Archived sources are included in every response, discovery-only calls included
* `action_source` - Type of event source (website pixel, app SDK, etc.)
* `surface` - Structured surface this source represents, when the flat `action_source` is too coarse
* `managed_by` - Who manages this source: `buyer` or `seller`
* `setup` - Implementation details (snippet, instructions)
* `health` - Event source health assessment (when seller supports health scoring)
* `errors` - Per-source errors (only when `action: "failed"`)
* `ext` - Per-source extension metadata, such as platform-native conversion IDs, attribution-window handles, or raw origin strings

**See schema for complete field list**: [sync-event-sources-response.json](https://adcontextprotocol.org/schemas/3.3.0-beta.0/media-buy/sync-event-sources-response.json)

### Platform identifier migration (AdCP 3.3)

`platform_id` identifies the event source in the seller's ad platform. Continue using `event_source_id` in AdCP requests, including [`log_event`](/dist/docs/3.3.0-beta.0/media-buy/task-reference/log_event); the platform ID is for cross-referencing the platform's object.

When reporting a platform identifier in 3.3 and later 3.x responses, sellers MUST emit both `platform_id` and the deprecated `seller_id` alias with identical values, including in discovery-only responses and for seller-managed sources. Both fields remain optional when no platform identifier is available. Buyers MUST prefer `platform_id` and fall back to `seller_id` only when `platform_id` is absent. A response containing only the legacy alias remains schema-valid for compatibility with older sellers.

The alias remains functional throughout 3.x. Removal is no earlier than AdCP 4.0 and follows the [six-month deprecation policy](/dist/docs/3.3.0-beta.0/reference/versioning#deprecation-policy). At this location, `seller_id` continues to mean the event source's platform ID; it MUST NOT be repurposed to identify the seller.

### Event Source Health

Sellers that evaluate event source quality include a `health` object on each source in the response. This is analogous to Snap's Event Quality Score or Meta's Event Match Quality — it tells the buyer whether their event integration is working well enough for optimization.

| Field | Type | Description |
| - | - | - |
| `status` | string | AdCP-standardized health level: `insufficient`, `minimum`, `good`, `excellent`. Use this for cross-seller decisions. |
| `detail` | object | Seller-specific scoring (optional). Contains `score`, `max_score`, and optional `label`. Only present when the seller has a native quality score. |
| `match_rate` | number | Fraction of events matched to ad interactions (0.0-1.0). |
| `last_event_at` | date-time | Timestamp of the most recent event received. |
| `evaluated_at` | date-time | When this health assessment was computed. Stale for sellers computing from reporting data. |
| `events_received_24h` | integer | Events received in the last 24 hours (0 = not firing). |
| `issues` | array | Actionable problems with severity (`error`, `warning`, `info`) and message. Sort by severity — don't rely on array position. |

```json test=false theme={null}
{
  "event_sources": [
    {
      "event_source_id": "web_pixel",
      "action": "unchanged",
      "managed_by": "buyer",
      "health": {
        "status": "good",
        "detail": { "score": 7.2, "max_score": 10, "label": "Event Match Quality" },
        "match_rate": 0.72,
        "last_event_at": "2026-03-23T14:22:00Z",
        "evaluated_at": "2026-03-23T14:30:00Z",
        "events_received_24h": 14200,
        "issues": [
          {
            "severity": "warning",
            "message": "Missing hashed_email parameter on 38% of purchase events. Adding this improves match rate for cross-device attribution."
          }
        ]
      }
    }
  ]
}
```

Buyer agents should key decisions off `status`, not `detail.score`. The four-tier status is comparable across all sellers — a buyer agent writes one rule ("require `good` or better for DR products") that works everywhere. The `detail` object is for human dashboards or advanced diagnostics.

Buyer agents can use health data to:

* Gate product selection on event quality (e.g., require `good` or better for DR products)
* Surface setup issues to the buyer before campaign launch
* Prioritize which event sources to fix first

## Common Scenarios

### Discovery Only

Discover all event sources on an account (including seller-managed sources) without making changes. Useful for platform-managed conversion tracking where the seller provides always-on attribution:

```javascript JavaScript theme={null}
import { testAgent } from "@adcp/sdk/testing";
import { SyncEventSourcesResponseSchema } from "@adcp/sdk";

const result = await testAgent.syncEventSources({
  account: { account_id: "acct_12345" },
});

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

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

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

if ("event_sources" in validated) {
  for (const source of validated.event_sources) {
    console.log(`${source.event_source_id} (${source.managed_by}): ${source.name}`);
  }
}
```

```python Python theme={null}
import asyncio
from adcp.testing import test_agent

async def main():
    result = await test_agent.simple.sync_event_sources(
        account={'account_id': 'acct_12345'}
    )

    if hasattr(result, 'errors') and result.errors:
        raise Exception(f"Operation failed: {result.errors}")

    for source in result.event_sources:
        print(f"{source.event_source_id} ({source.managed_by}): {source.name}")

asyncio.run(main())
```

### Multiple Event Sources

Configure separate sources for website and app:

```javascript JavaScript theme={null}
import { testAgent } from "@adcp/sdk/testing";
import { SyncEventSourcesResponseSchema } from "@adcp/sdk";

const result = await testAgent.syncEventSources({
  account: { account_id: "acct_12345" },
  event_sources: [
    {
      event_source_id: "web_pixel",
      name: "Website Pixel",
      event_types: ["purchase", "lead", "add_to_cart", "view_content"],
      allowed_domains: ["www.example.com"],
    },
    {
      event_source_id: "app_sdk",
      name: "Mobile App SDK",
      event_types: ["purchase", "app_install", "app_launch"],
    },
  ],
});

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

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

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

if ("event_sources" in validated) {
  for (const source of validated.event_sources) {
    console.log(`${source.event_source_id} (${source.managed_by}): ${source.action}`);
  }
}
```

```python Python theme={null}
import asyncio
from adcp.testing import test_agent

async def main():
    result = await test_agent.simple.sync_event_sources(
        account={'account_id': 'acct_12345'},
        event_sources=[
            {
                'event_source_id': 'web_pixel',
                'name': 'Website Pixel',
                'event_types': ['purchase', 'lead', 'add_to_cart', 'view_content'],
                'allowed_domains': ['www.example.com']
            },
            {
                'event_source_id': 'app_sdk',
                'name': 'Mobile App SDK',
                'event_types': ['purchase', 'app_install', 'app_launch']
            }
        ]
    )

    if hasattr(result, 'errors') and result.errors:
        raise Exception(f"Operation failed: {result.errors}")

    for source in result.event_sources:
        print(f"{source.event_source_id} ({source.managed_by}): {source.action}")

asyncio.run(main())
```

### Discovering Seller-Managed Sources

Sellers may provide always-on event sources (e.g. Amazon sales attribution). These appear in the response with `managed_by: "seller"` alongside your buyer-managed sources:

```json theme={null}
{
  "$schema": "https://adcontextprotocol.org/schemas/3.3.0-beta.0/media-buy/sync-event-sources-response.json",
  "status": "completed",
  "event_sources": [
    {
      "event_source_id": "web_pixel",
      "name": "Website Pixel",
      "platform_id": "px_abc123",
      "seller_id": "px_abc123",
      "action": "created",
      "managed_by": "buyer",
      "setup": {
        "snippet": "<script src=\"https://seller.example/pixel/px_abc123.js\"></script>",
        "snippet_type": "javascript",
        "instructions": "Place this tag in the <head> of all pages where you want to track events."
      }
    },
    {
      "event_source_id": "seller_sales_attribution",
      "name": "Sales Attribution",
      "platform_id": "internal_attr",
      "seller_id": "internal_attr",
      "action": "unchanged",
      "managed_by": "seller",
      "action_source": "in_store"
    }
  ]
}
```

Products with `conversion_tracking.platform_managed: true` indicate the seller provides these sources.

### Creator and Owned-Property Sources

Seller-managed creator or owned-property sources use the same `event_sources` array, with a flat `action_source` for compatibility and `surface` for structured context:

```json theme={null}
{
  "$schema": "https://adcontextprotocol.org/schemas/3.3.0-beta.0/media-buy/sync-event-sources-response.json",
  "status": "completed",
  "event_sources": [
    {
      "event_source_id": "creator_channel",
      "name": "Creator Channel Events",
      "platform_id": "creator_attr_001",
      "seller_id": "creator_attr_001",
      "event_types": ["follow", "content_view", "watch_milestone"],
      "action_source": "system_generated",
      "surface": {
        "category": "owned_property",
        "property_type": "channel",
        "namespace": "video_platform",
        "property_id": "channel_123"
      },
      "managed_by": "seller",
      "action": "unchanged",
      "ext": {
        "video_platform": {
          "native_origin": "owned_channel"
        }
      }
    }
  ]
}
```

Use `follow` for free durable opt-ins to a channel, profile, feed, list, or podcast. Use `subscribe` only for paid subscriptions or paid memberships.

### Clean Sync with delete\_missing

Replace all buyer-managed event sources on the account:

```javascript JavaScript theme={null}
import { testAgent } from "@adcp/sdk/testing";
import { SyncEventSourcesResponseSchema } from "@adcp/sdk";

const result = await testAgent.syncEventSources({
  account: { account_id: "acct_12345" },
  delete_missing: true,
  event_sources: [
    {
      event_source_id: "unified_pixel",
      name: "Unified Tracking Pixel",
    },
  ],
});

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

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

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

if ("event_sources" in validated) {
  for (const source of validated.event_sources) {
    if (source.action === "deleted") {
      console.log(`Removed: ${source.event_source_id}`);
    } else {
      console.log(`Active: ${source.event_source_id} (${source.action})`);
    }
  }
}
```

```python Python theme={null}
import asyncio
from adcp.testing import test_agent

async def main():
    result = await test_agent.simple.sync_event_sources(
        account={'account_id': 'acct_12345'},
        delete_missing=True,
        event_sources=[{
            'event_source_id': 'unified_pixel',
            'name': 'Unified Tracking Pixel'
        }]
    )

    if hasattr(result, 'errors') and result.errors:
        raise Exception(f"Operation failed: {result.errors}")

    for source in result.event_sources:
        if source.action == 'deleted':
            print(f"Removed: {source.event_source_id}")
        else:
            print(f"Active: {source.event_source_id} ({source.action})")

asyncio.run(main())
```

### Archiving an event source

`delete_missing` is the only way to remove sources in bulk, and it cannot be undone. To take one source out of use and keep its configuration, send `archived: true` on that item. To bring it back, send `archived: false`.

```json test=false theme={null}
{
  "account": { "account_id": "acct_12345" },
  "event_sources": [
    { "event_source_id": "web_pixel", "archived": true }
  ]
}
```

The response reports `action: "archived"` and echoes `archived: true`. This and the request above are illustrative fragments, so both fences are `test=false`:

```json test=false theme={null}
{
  "event_sources": [
    { "event_source_id": "web_pixel", "action": "archived", "archived": true, "managed_by": "buyer" }
  ]
}
```

* **Only change.** When `archived` is present on an item, every other field except `event_source_id` is ignored, the same way [`sync_audiences`](/dist/docs/3.3.0-beta.0/media-buy/task-reference/sync_audiences) treats `delete: true`. To change both, make two separate calls. Buyers SHOULD omit `archived` unless changing archive state, because a seller ignores the rest of an item that carries it, `archived: false` included.
* **While archived.** The source keeps its configuration and seller-side identity. It stops accepting events from [`log_event`](/dist/docs/3.3.0-beta.0/media-buy/task-reference/log_event) and stops resolving for new optimization goals.
* **Reversal.** Undoing an archive reports `updated`, since it changes one field of the source.
* **Idempotent.** Archiving an archived source, or undoing an archive on an active one, reports `unchanged`.
* **Discovery.** Archived sources stay in every response, flagged `archived: true`, so buyers auditing an account can find and restore them and pagination stays stable. A source this call did not change reports `unchanged`. Filter on `archived` client-side.
* **`delete_missing`.** Archived sources are not removed by `delete_missing`, so a buyer that leaves an archived source out of its desired set does not lose the configuration it can restore.
* **Capability gate.** Sellers declare `media_buy.event_source_archive: true` in [`get_adcp_capabilities`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities). A seller without it MUST reject the whole request with an operation-level [`UNSUPPORTED_FEATURE`](/dist/docs/3.3.0-beta.0/building/verification/compliance-catalog#error-code-unsupported-feature) error and apply none of its items, so a buyer never mistakes an ignored field for an archive. Sellers return `action: "archived"` only to callers that sent `archived`, which keeps clients that validate `action` strictly against the older enum working. A seller that predates this field may ignore it and report `updated` or `unchanged`, so buyers check the capability first and confirm the echoed `archived: true`.

## Setup Instructions

The response includes setup details for each event source. The `setup` object tells you how to activate the source:

| `snippet_type` | Description | Action Required |
| - | - | - |
| `javascript` | JavaScript tag for website pages | Place in `<head>` of tracked pages |
| `html` | HTML pixel/iframe | Place before `</body>` |
| `pixel_url` | URL that fires on events | Send GET request on each event |
| `server_only` | No client-side tag needed | Use [`log_event`](/dist/docs/3.3.0-beta.0/media-buy/task-reference/log_event) API directly |

## Error Handling

| Error Code | Description | Resolution |
| - | - | - |
| [`ACCOUNT_NOT_FOUND`](/dist/docs/3.3.0-beta.0/building/verification/compliance-catalog#error-code-account-not-found) | Account does not exist | Verify `account_id` from account setup |
| [`UNSUPPORTED_FEATURE`](/dist/docs/3.3.0-beta.0/building/verification/compliance-catalog#error-code-unsupported-feature) | An event type is not included in the seller's `supported_event_types`, or the request sends `archived` to a seller without `media_buy.event_source_archive` | Choose a supported event type or another seller; check [`get_adcp_capabilities`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities) before archiving |
| [`VALIDATION_ERROR`](/dist/docs/3.3.0-beta.0/building/verification/compliance-catalog#error-code-validation-error) | An `event_source_id` is duplicated in the request | Use unique source IDs |
| [`RATE_LIMITED`](/dist/docs/3.3.0-beta.0/building/verification/compliance-catalog#error-code-rate-limited) | Too many sync requests | Wait and retry with exponential backoff |

## Best Practices

1. **Sync before logging** - Always configure event sources before sending events via [`log_event`](/dist/docs/3.3.0-beta.0/media-buy/task-reference/log_event). Events sent to unconfigured sources will be rejected.

2. **Use descriptive IDs** - Choose `event_source_id` values that are meaningful (e.g. `web_pixel`, `app_sdk`, `crm_import`) rather than opaque identifiers.

3. **Specify event\_types** - Restrict each source to relevant event types for better validation and debugging.

4. **Check seller capabilities** - Use [`get_adcp_capabilities`](/dist/docs/3.3.0-beta.0/protocol/get_adcp_capabilities) to discover supported event types, UID types, and action sources before configuring event sources.

5. **Install setup snippets** - When the response includes `setup` instructions, install the provided snippet before logging events. Server-only sources (`snippet_type: "server_only"`) skip this step.

6. **Handle seller-managed sources** - The response may include sources with `managed_by: "seller"` that you didn't configure. These are always-on and provide additional attribution data.

## Next Steps

* [Conversion Tracking](/dist/docs/3.3.0-beta.0/media-buy/conversion-tracking/) - Data model, optimization goals, and the end-to-end flow
* [log\_event](/dist/docs/3.3.0-beta.0/media-buy/task-reference/log_event) - Send marketing events to configured event sources
* [create\_media\_buy](/dist/docs/3.3.0-beta.0/media-buy/task-reference/create_media_buy#campaign-with-conversion-optimization) - Set optimization goals on packages referencing event sources
* [get\_media\_buy\_delivery](/dist/docs/3.3.0-beta.0/media-buy/task-reference/get_media_buy_delivery) - Monitor conversion metrics in delivery reports


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.