> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apostra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Log Events

> Send custom events to an advertiser for downstream measurement and reporting

## Overview

`POST /api/v2/buyer/advertisers/:advertiserId/log-event` is a single-call ingest endpoint for sending outcome or marketing events to an advertiser. It accepts the standard ADCP event types — `purchase`, `refund`, `lead`, `add_to_cart`, `initiate_checkout`, `view_content`, `content_view`, `watch_milestone`, `follow`, `complete_registration`, `subscribe`, `start_trial`, `page_view`, `app_install`, `app_launch` — and a `custom` event type for events that don't map to any of those.

Use it for:

* **Bespoke conversion definitions** — a "qualified demo booked" or "tier upgrade" event that doesn't fit the standard taxonomy. Send `event_type: custom` with `custom_event_name`.
* **Internal-only events for ML training** — engagement signals you want to feed measurement, even if they aren't reportable conversions.
* **Custom funnel steps** — domain-specific milestones (game level reached, KYC step completed) that drive your bidding logic.
* **Server-to-server batch ingestion** — up to 10,000 events per call.

## How it differs from event sources and measurement data

The three measurement ingest paths overlap; pick the one that matches the shape of your data.

| Surface | What you send | Granularity | Best for |
| - | - | - | - |
| **Event sources** (`/event-sources/sync`) | The *configuration* of where events come from (pixels, SDKs, MMPs) | Source registration | Onboarding a new pixel or feed once, then sending events through it |
| **Log events** (`/log-event`) | Per-user, per-event records keyed to a registered `event_source_id` | One record per event (1–10,000 per call) | Bespoke conversion taxonomies, custom funnel steps, server-to-server event ingestion |
| **Measurement data** (`/measurement-data/sync`) | Pre-aggregated outcomes for a time window and entity | One record per metric × window × campaign | MMP, CRM, or warehouse exports of weekly/daily aggregates |

`log-event` is the path for **identified, per-user events at advertiser scope**. Event sources are the *configuration layer* you call once; `log-event` is the runtime path you call repeatedly.

<Note>
  Every event must reference an `event_source_id` that is already registered
  on the advertiser via `POST /advertisers/:advertiserId/event-sources/sync`.
  Events sent against an unregistered source are rejected. See the
  [Measurement & Incrementality guide](/v2/guides/measurement-incrementality)
  for event-source setup.
</Note>

## Request shape

All examples use:

```
BASE = https://api.apostra.com/api/v2/buyer
AUTH = Authorization: Bearer scope3_<your_api_key>
```

```bash theme={null}
curl -X POST "$BASE/advertisers/12345/log-event" \
  -H "Authorization: Bearer scope3_<your_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "event_source_id": "website_pixel",
    "events": [
      {
        "event_id": "txn_abc123",
        "event_type": "purchase",
        "event_time": "2026-04-26T14:30:00-05:00",
        "action_source": "website",
        "event_source_url": "https://shop.example.com/checkout/complete",
        "user_match": {
          "hashed_email": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
          "click_id": "fb.1.1700000000.AbCdEfGh",
          "click_id_type": "fbclid"
        },
        "custom_data": {
          "value": 89.99,
          "currency": "USD",
          "order_id": "order_55512",
          "content_ids": ["sku_red_shoe_42"],
          "num_items": 1
        }
      },
      {
        "event_id": "demo_booked_98765",
        "event_type": "custom",
        "custom_event_name": "qualified_demo_booked",
        "event_time": "2026-04-26T15:05:00-05:00",
        "action_source": "website",
        "user_match": {
          "hashed_email": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
        }
      }
    ]
  }'
```

```json Response theme={null}
{
  "events_received": 2,
  "events_processed": 2,
  "warnings": [],
  "match_quality": 0.87
}
```

### Top-level fields

| Field | Type | Required | Notes |
| - | - | - | - |
| `event_source_id` | string | yes | Must match an `event_source_id` registered on the advertiser |
| `events` | array | yes | 1–10,000 events per call |
| `test_event_code` | string | no | Marks the request as test traffic — events are validated but excluded from production data |

### Per-event fields

| Field | Type | Required | Notes |
| - | - | - | - |
| `event_id` | string | yes | Unique within `event_type` + `event_source_id`. Resends with the same `event_id` are deduplicated. |
| `event_type` | enum | yes | Canonical ADCP event type such as `purchase`, `lead`, `add_to_cart`, `initiate_checkout`, `view_content`, `content_view`, `watch_milestone`, `follow`, `complete_registration`, `page_view`, `app_install`, `subscribe`, `start_trial`, `refund`, or `custom` |
| `event_time` | ISO 8601 | yes | Must include offset (e.g. `2026-04-26T14:30:00-05:00`) |
| `custom_event_name` | string | conditional | Required when `event_type: custom` |
| `action_source` | enum | no | `website`, `app`, `in_store`, `phone_call`, `system_generated`, `other` |
| `event_source_url` | URL | conditional | Required when `action_source: website` |
| `user_match` | object | no | Identity fields for attribution (see below) |
| `custom_data` | object | no | Value, currency, order ID, items |

### `user_match` — identity fields

Send at least one identifier per event. More identifiers = higher match rate.

| Field | Format | Notes |
| - | - | - |
| `uids` | array of `{ type, value }` | Universal IDs: `rampid`, `id5`, `uid2`, `euid`, `pairid`, `maid` |
| `hashed_email` | 64-char lowercase hex | SHA-256 of lowercased, trimmed email |
| `hashed_phone` | 64-char lowercase hex | SHA-256 of E.164-formatted phone |
| `click_id` + `click_id_type` | string | Platform click identifier (`fbclid`, `gclid`, `ttclid`) |
| `client_ip` | string | IP for probabilistic matching |
| `client_user_agent` | string | UA for probabilistic matching |

### `custom_data` — event payload

| Field | Type | Notes |
| - | - | - |
| `value` | number | Monetary value of the event |
| `currency` | string | ISO 4217 (`USD`, `EUR`, `GBP`) — required when `value` is set |
| `order_id` | string | Unique transaction ID |
| `content_ids` | array of strings | SKU / product IDs |
| `content_type` | string | Category (`product`, `service`) |
| `num_items` | integer | Item count |
| `contents` | array of `{ id, quantity, price, brand }` | Per-item detail |

### Response shape

| Field | Type | Notes |
| - | - | - |
| `events_received` | integer | Count of events in the request |
| `events_processed` | integer | Count successfully queued |
| `partial_failures` | array | Per-event failures with `event_id`, `code`, `message` |
| `warnings` | array of strings | Non-fatal issues — low match quality, missing optional fields |
| `match_quality` | number (0–1) | Aggregate match-quality score for the batch |

A `200` with `partial_failures` is normal — bad events are rejected individually, the rest succeed. Treat any failure as something to fix at source, not a transient error.

## Best practices

<AccordionGroup>
  <Accordion title="Pick stable, descriptive custom_event_name values">
    Custom event names become the join key for downstream measurement and
    reporting. Pick names you won't want to change (`qualified_demo_booked`,
    `kyc_step_2_complete`). Renaming after launch breaks historical
    continuity.
  </Accordion>

  <Accordion title="Use a standard event_type when one fits">
    If your event is genuinely a purchase or a lead, use `purchase` or
    `lead`. Standard types get first-class treatment in measurement and
    optimization; `custom` events fall back to generic handling.
  </Accordion>

  <Accordion title="Use event_id for idempotency">
    `event_id` is the dedup key, scoped to `event_type` + `event_source_id`.
    Re-sending the same `event_id` is safe — duplicates are dropped. Use
    your stable upstream identifier (transaction ID, demo booking ID) so
    retries don't double-count.
  </Accordion>

  <Accordion title="Send events in real time when you can">
    Recent events match better — click IDs and probabilistic signals decay
    quickly. Aim for sub-hour latency for behavioral events; daily batches
    are fine for offline conversions like CRM exports.
  </Accordion>

  <Accordion title="Use test_event_code in non-prod">
    Set `test_event_code` for test traffic. Events validate end-to-end
    but are excluded from production reporting and the measurement engine.
  </Accordion>

  <Accordion title="Batch up to 10,000 events">
    The endpoint accepts up to 10,000 events per call. Batch larger flows
    rather than firing one HTTP request per event — same data, much less
    overhead.
  </Accordion>
</AccordionGroup>

## Privacy

<Warning>
  **Never send raw PII.** No plaintext emails, phone numbers, names, or
  addresses. Only pre-hashed identifiers (SHA-256, lowercase, 64-char hex)
  or pre-resolved identity tokens (RampID, UID2, ID5, etc.). Normalize the
  email (lowercase, trimmed) and phone (E.164) **before** hashing — see the
  [Conversion API guide](/v2/integrations/conversion-api) for hashing
  rules. Malformed hashes are silently dropped.
</Warning>

The same privacy contract applies whether an event is `purchase` or `custom`. Custom events are not a back door for raw PII.

## Related

<CardGroup cols={2}>
  <Card title="Conversion API" href="/v2/integrations/conversion-api" icon="bolt">
    Detailed identity-hashing rules and the same `log-event` payload viewed
    as a conversion-tracking integration.
  </Card>

  <Card title="Measurement & Incrementality" href="/v2/guides/measurement-incrementality" icon="chart-line">
    Register event sources, configure measurement, and run incrementality
    tests against the events you log.
  </Card>
</CardGroup>


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