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

# Trigger an event

> Issue a publishable tracking key and send engagement events from a browser

This walkthrough issues a publishable tracking key, sends engagement events
to `POST /v1/track` with plain HTTP, and shows the SDK equivalent at the end.
You need your `org_` API key for step 1; after that, everything runs with the
publishable key.

```bash theme={null}
export API_BASE_URL="https://api.develop.fourdos.dev"
export API_KEY="org_…"
```

## 1. Issue a tracking key

Tracking keys are **publishable** - they ship in your client code like an
analytics measurement id. Their abuse controls are an origin allowlist and
per-key rate limiting, not secrecy. Issue one with your server-side API key:

```bash theme={null}
curl -X POST "$API_BASE_URL/v1/engagement/keys" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Main website",
    "allowed_origins": ["https://www.example.com", "http://localhost:5173"]
  }'
```

`201 Created`:

```json theme={null}
{
  "id": "…",
  "organization_id": "…",
  "name": "Main website",
  "key": "ek_live_…",
  "allowed_origins": ["https://www.example.com", "http://localhost:5173"],
  "status": "active",
  "last_used_at": null,
  "revoked_at": null,
  "created_at": "2026-07-17T00:00:00.000Z",
  "updated_at": "2026-07-17T00:00:00.000Z"
}
```

Browser requests are only accepted from the origins you listed. `GET
/v1/engagement/keys` lists your keys; `DELETE /v1/engagement/keys/{id}`
revokes one.

## 2. Send events over plain HTTP

`POST /v1/track` takes the tracking key as `?key=` (or a `key` field in the
body) - no `Authorization` header. Events must identify a **known member**,
by the `external_id` you gave them at creation or by platform `user_id`.
Batch up to 20 events per request:

```bash theme={null}
curl -X POST "$API_BASE_URL/v1/track?key=ek_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "member_external_id": "cust-001",
        "tags": ["world_cup_26", "england", "kane"],
        "event_id": "3f8e7a10-0000-4000-8000-000000000001"
      },
      {
        "member_external_id": "cust-001",
        "action": "content_view",
        "tags": ["world_cup_26", "highlights"],
        "metadata": { "content_id": "article-99" },
        "event_id": "3f8e7a10-0000-4000-8000-000000000002"
      }
    ]
  }'
```

`202 Accepted`, with a per-event outcome:

```json theme={null}
{
  "results": [
    { "status": "accepted", "id": "…", "event_id": "3f8e7a10-0000-4000-8000-000000000001" },
    { "status": "accepted", "id": "…", "event_id": "3f8e7a10-0000-4000-8000-000000000002" }
  ]
}
```

The same works from browser `fetch` - the endpoint is CORS-enabled:

```js theme={null}
await fetch(`https://api.develop.fourdos.dev/v1/track?key=${TRACKING_KEY}`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    events: [{ member_external_id: "cust-001", tags: ["world_cup_26", "kane"] }]
  })
});
```

Things to know:

* **`event_id` is your retry safety.** Send a client-generated UUID per event
  and the server dedupes on it - a retried request reports `duplicate` for
  events that already landed, and nothing double-counts.
* **Tags** are trimmed and lowercased, must match
  `^[a-z0-9][a-z0-9_.:-]{0,63}$`, max 10 per event. Tags drive the member's
  affinity counters - see [Events](/concepts/events).
* **Unknown members are rejected, not stored.** An event for an id that
  doesn't resolve to a member of your org comes back
  `{ "status": "rejected", "reason": … }` in the 202 envelope.
* A single event can also be posted without the `events` wrapper (beacon
  shorthand): `{ "key": "ek_live_…", "member_external_id": "…", "tags": […] }`.

## 3. The SDK equivalent

The [engagement SDK](/sdk/engagement-sdk) wraps this endpoint with batching,
auto-flush, retry with dedupe, and `sendBeacon` drain on page hide:

```ts theme={null}
import { createEngagementClient } from "engagement-sdk";

const engage = createEngagementClient({
  key: "ek_live_…",
  endpoint: "https://api.develop.fourdos.dev"
});

engage.identify("cust-001");
engage.track(["world_cup_26", "england", "kane"]);
engage.track(["world_cup_26", "highlights"], {
  action: "content_view",
  metadata: { content_id: "article-99" }
});
```

## What events set in motion

Accepted events update the member's tag affinity and flow onto the platform
event bus, where your organization's XP rules are evaluated against them -
matching events award XP automatically. See [XP](/concepts/xp).
