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

# Tracking events

> Send idempotent metered events and control subscription attribution with nozle.track().

`nozle.track()` sends one event to `eventsUrl/api/v1/events`.

```ts theme={null}
await nozle.track(
  'workspace_123',
  'api_calls',
  { model: 'example-model', tokens: 1_500 },
  {
    subscriptionId: 'workspace_123_subscription',
    transactionId: 'request_0183f',
    timestamp: '2026-08-04T10:30:00Z',
  },
)
```

## Signature

```ts theme={null}
track(
  customerId: string,
  event: string,
  metadata?: Record<string, unknown>,
  options?: {
    subscriptionId?: string
    transactionId?: string
    timestamp?: string
  },
): Promise<void>
```

| Argument         | Description                                                             |
| ---------------- | ----------------------------------------------------------------------- |
| `customerId`     | External Nozle customer ID.                                             |
| `event`          | Event code matching the Feature code.                                   |
| `metadata`       | Properties used by metric aggregation or cost attribution.              |
| `subscriptionId` | Explicit external subscription target.                                  |
| `transactionId`  | Stable event ID used for idempotency. A UUID is generated when omitted. |
| `timestamp`      | Optional ISO 8601 event time.                                           |

## Subscription resolution

When `subscriptionId` is omitted, the SDK queries `eventsUrl/api/v1/subscriptions` for active subscriptions belonging to the customer:

* exactly one active subscription: its external ID is cached and used;
* no active subscriptions: `track()` rejects;
* multiple active subscriptions: `track()` rejects and requires an explicit `subscriptionId`.

Pass `subscriptionId` explicitly for customers with multiple active subscriptions and for latency-sensitive event paths that should avoid the initial lookup.

## Request payload

```json theme={null}
{
  "event": {
    "transaction_id": "request_0183f",
    "external_customer_id": "workspace_123",
    "external_subscription_id": "workspace_123_subscription",
    "code": "api_calls",
    "properties": {
      "model": "example-model",
      "tokens": 1500
    },
    "timestamp": "2026-08-04T10:30:00Z"
  }
}
```

## Idempotency

Generate a deterministic transaction ID and capture the event timestamp from the business operation whenever possible. Retry the same logical event with both values unchanged.

```ts theme={null}
const transactionId = `generation:${generationId}:completed`
const timestamp = generationCompletedAt.toISOString()

await nozle.track(customerId, 'text_generation', {
  input_tokens: usage.inputTokens,
  output_tokens: usage.outputTokens,
}, { subscriptionId, transactionId, timestamp })
```

## Operational guidance

* Await `track()` when the request must know whether ingestion succeeded.
* For high-volume workloads, publish through your own durable queue and call `track()` from a worker.
* Do not create a new random transaction ID on each retry.
* Do not regenerate the timestamp when retrying an accepted business event.
* Match property names to the Feature aggregation configuration exactly.
* Use `usage.track()` instead when the operation must atomically enforce and deduct product credits.
