# Track an event

`POST /v1/events/track`

Requires an API key with the `events:write` scope. [About keys and scopes](/docs/api/authentication)

Takes an `Idempotency-Key` header: a retry with the same key returns the first result instead of acting twice. Derive the key from what you are sending, and reuse it on every retry. [About idempotency](/docs/api/idempotency)

Records a behavioural event against a contact. Events drive journey triggers and segment rules, so this is the main ingestion endpoint for product activity. The event and its processing obligation are stored atomically: a 201 confirms storage, while journey and conversion effects run asynchronously afterwards. Name the contact by exactly one of `contactId`, `externalId` or `email`; a contact that does not exist is a 404, never created. `occurredAt` may be at most 24 hours in the future. Names are case-sensitive, up to 128 characters with no control characters; those beginning with `contact.` are reserved for platform events. A workspace tracks at most 1,000 distinct names: a new one past that is refused with `EVENT_NAME_LIMIT_REACHED`, so keep ids and prices in `properties`. A repeated `Idempotency-Key` answers the event first stored, with `Idempotency-Replay: true`.

## Header parameters

- `Idempotency-Key` (string, required, 8–255 characters)

## Request body

- One of: **By contact id**
  - `contactId` (string, required, uuid) — Kirora's id for the contact.
  - `eventName` (string, required, 1–128 characters) — Case-sensitive, up to 128 characters; `contact.*` is reserved.
  - `properties` (object, optional, default `{}`) — Free-form details of the event; at most 16 KB, keys up to 64 characters.
  - `occurredAt` (string, optional, date-time) — When it happened; defaults to now. At most 24 hours ahead.
  - `context` (EventContextInput, optional) — Where and on what it happened, beside the properties.
    - `sessionId` (string, optional, up to 128 characters) — The visit, from the SDK.
    - `locale` (string, optional, up to 35 characters) — As BCP 47, `en-GB`.
    - `timezone` (string, optional, up to 64 characters) — As IANA, `Europe/Berlin`.
    - `page` (object, optional) — Web: the page, without its query string or fragment.
      - `url` (string, optional, up to 2048 characters)
      - `path` (string, optional, up to 2048 characters)
      - `title` (string, optional, up to 256 characters)
      - `referrer` (string, optional, up to 2048 characters)
    - `campaign` (object, optional) — Web: the `utm_*` parameters of the page.
      - `source` (string, optional, up to 256 characters)
      - `medium` (string, optional, up to 256 characters)
      - `name` (string, optional, up to 256 characters)
      - `term` (string, optional, up to 256 characters)
      - `content` (string, optional, up to 256 characters)
    - `device` (object, optional)
      - `id` (string, optional, up to 128 characters) — A random id the SDK keeps on the device; never a hardware id.
    - `library` (object, optional)
      - `name` (string, optional, up to 64 characters)
      - `version` (string, optional, up to 64 characters)
    - `ip` (string, optional, up to 64 characters)
    - `userAgent` (string, optional, up to 1024 characters)
- One of: **By external id**
  - `externalId` (string, required, 1–255 characters) — Your own id for the contact, as set with identify.
  - `eventName` (string, required, 1–128 characters) — Case-sensitive, up to 128 characters; `contact.*` is reserved.
  - `properties` (object, optional, default `{}`) — Free-form details of the event; at most 16 KB, keys up to 64 characters.
  - `occurredAt` (string, optional, date-time) — When it happened; defaults to now. At most 24 hours ahead.
  - `context` (EventContextInput, optional) — Where and on what it happened, beside the properties.
    - `sessionId` (string, optional, up to 128 characters) — The visit, from the SDK.
    - `locale` (string, optional, up to 35 characters) — As BCP 47, `en-GB`.
    - `timezone` (string, optional, up to 64 characters) — As IANA, `Europe/Berlin`.
    - `page` (object, optional) — Web: the page, without its query string or fragment.
      - `url` (string, optional, up to 2048 characters)
      - `path` (string, optional, up to 2048 characters)
      - `title` (string, optional, up to 256 characters)
      - `referrer` (string, optional, up to 2048 characters)
    - `campaign` (object, optional) — Web: the `utm_*` parameters of the page.
      - `source` (string, optional, up to 256 characters)
      - `medium` (string, optional, up to 256 characters)
      - `name` (string, optional, up to 256 characters)
      - `term` (string, optional, up to 256 characters)
      - `content` (string, optional, up to 256 characters)
    - `device` (object, optional)
      - `id` (string, optional, up to 128 characters) — A random id the SDK keeps on the device; never a hardware id.
    - `library` (object, optional)
      - `name` (string, optional, up to 64 characters)
      - `version` (string, optional, up to 64 characters)
    - `ip` (string, optional, up to 64 characters)
    - `userAgent` (string, optional, up to 1024 characters)
- One of: **By email**
  - `email` (string, required) — The contact's email address, matched trimmed and without case.
  - `eventName` (string, required, 1–128 characters) — Case-sensitive, up to 128 characters; `contact.*` is reserved.
  - `properties` (object, optional, default `{}`) — Free-form details of the event; at most 16 KB, keys up to 64 characters.
  - `occurredAt` (string, optional, date-time) — When it happened; defaults to now. At most 24 hours ahead.
  - `context` (EventContextInput, optional) — Where and on what it happened, beside the properties.
    - `sessionId` (string, optional, up to 128 characters) — The visit, from the SDK.
    - `locale` (string, optional, up to 35 characters) — As BCP 47, `en-GB`.
    - `timezone` (string, optional, up to 64 characters) — As IANA, `Europe/Berlin`.
    - `page` (object, optional) — Web: the page, without its query string or fragment.
      - `url` (string, optional, up to 2048 characters)
      - `path` (string, optional, up to 2048 characters)
      - `title` (string, optional, up to 256 characters)
      - `referrer` (string, optional, up to 2048 characters)
    - `campaign` (object, optional) — Web: the `utm_*` parameters of the page.
      - `source` (string, optional, up to 256 characters)
      - `medium` (string, optional, up to 256 characters)
      - `name` (string, optional, up to 256 characters)
      - `term` (string, optional, up to 256 characters)
      - `content` (string, optional, up to 256 characters)
    - `device` (object, optional)
      - `id` (string, optional, up to 128 characters) — A random id the SDK keeps on the device; never a hardware id.
    - `library` (object, optional)
      - `name` (string, optional, up to 64 characters)
      - `version` (string, optional, up to 64 characters)
    - `ip` (string, optional, up to 64 characters)
    - `userAgent` (string, optional, up to 1024 characters)

## Returns

Returns `201 Created` with a `TrackedEvent` in `data`. Otherwise returns an error; [Errors](/docs/api/errors) lists every code and which to retry.

- `id` (string, required, uuid)
- `occurredAt` (string, required, date-time)

## Example: By Kirora contact id

```bash
curl -X POST https://api.kirora.io/v1/events/track \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Idempotency-Key: cart-7731-abandoned" \
  -H "Content-Type: application/json" \
  -d '{
    "contactId": "0192f3a1-7c2e-7d4b-9a10-3e5f6a7b8c9d",
    "eventName": "Cart Abandoned",
    "occurredAt": "2026-10-01T09:30:00Z"
  }'
```

Response `201`:

```json
{
  "data": {
    "id": "0192f3c0-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
    "occurredAt": "2026-10-01T09:30:00.000Z"
  }
}
```

## Example: By your own id, with properties

```bash
curl -X POST https://api.kirora.io/v1/events/track \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Idempotency-Key: order-1001-completed" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "customer-4821",
    "eventName": "Order Completed",
    "properties": {
      "order_id": "1001",
      "total": 46.5,
      "currency": "USD",
      "products": [
        {
          "name": "Smoked paprika",
          "quantity": 3,
          "price": 15.5
        }
      ]
    },
    "occurredAt": "2026-10-01T09:30:00Z"
  }'
```

Response `201`:

```json
{
  "data": {
    "id": "0192f3c0-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
    "occurredAt": "2026-10-01T09:30:00.000Z"
  }
}
```

## Example: By email address

```bash
curl -X POST https://api.kirora.io/v1/events/track \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Idempotency-Key: checkout-5520-started" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "amaka@example.com",
    "eventName": "Checkout Started"
  }'
```

Response `201`:

```json
{
  "data": {
    "id": "0192f3c0-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
    "occurredAt": "2026-10-01T09:30:00.000Z"
  }
}
```
