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

## Send an event

```go theme={null}
import nozle "github.com/nozle-dev/nozle-go"

client := nozle.New("") // reads NOZLE_SECRET_KEY

err := client.Track(ctx, nozle.TrackParams{
  CustomerID: "workspace_123", Event: "api_calls",
  SubscriptionID: "workspace_123_subscription",
  TransactionID: "request_0183f",
  Metadata: map[string]any{"tokens": 1500},
})
_ = err
```

`Track` posts `{ "event": { ... } }` to Engine `/api/v1/events`. Reuse the same transaction ID and timestamp when retrying a logical event. `Meter` posts a batch of 1–100 events to Engine `/api/v1/events/batch`; each batch event needs an explicit subscription ID. A batch of 0 or more than 100 events returns the plain error `nozle: batch size must be 1-100` before any request.

`TrackParams` contains `CustomerID`, `Event`, optional JSON `Metadata`, `SubscriptionID`, `TransactionID`, and `Timestamp`.

## Transaction IDs

`Track` returns only an `error`, so `id, err := client.Track(...)` does not compile. When `TransactionID` is empty, the SDK generates a UUIDv7 that your code never sees, and a retry cannot reuse it. Create the ID yourself, pass it, and keep it for retries:

```go theme={null}
txID := nozle.CreateTransactionID()
err := client.Track(ctx, nozle.TrackParams{
  CustomerID: "workspace_123", Event: "api_calls",
  SubscriptionID: "workspace_123_subscription",
  TransactionID: txID,
})
_ = err
```

A timeout, a network error or a `5xx` can arrive after Nozle recorded the event. Nozle records an event once per transaction ID, so retrying with the same `TransactionID`, the same event and the same `Timestamp` does not bill it twice; a new ID is a new event. Set `Timestamp` too when you may retry: without it, each attempt is stamped with its own arrival time.

`Meter` writes the ID it generates for an event without one back into your slice (`events[i].TransactionID`) before the first attempt, from `v0.12.1`. Retry with the same slice after an error or a timeout and the same IDs are sent; build a new slice only for new events.

## Subscription resolution

When `SubscriptionID` is empty, the SDK requests the customer's active subscriptions before it sends the event. From `v0.12.0` it remembers the result for the life of the `Client`, so two `Track` calls make three requests (before `v0.12.0` every call looked it up). `Checkout`, `CheckoutInvoice`, `CheckoutStatus`, `VerifyCheckout`, `Subscribe`, `CancelSubscription` and `ApplySubscriptionTransition` forget the entry, so the next call looks it up again. A change made elsewhere, for example in the dashboard, is not seen by a running `Client`; pass `SubscriptionID` when that matters. The customer must have exactly one active subscription; otherwise `Track` returns `nozle: track: exactly one active subscription required; supply SubscriptionID`. Subscribing a customer to a second plan adds a second active subscription rather than replacing the first, so a lookup that worked before can start failing. Resolve the subscription once, store it, and pass `SubscriptionID` on every call.

## Timestamps

`TrackParams.Timestamp` is a string: RFC 3339 with an offset (`2026-07-01T12:00:00Z`) or Unix seconds (`1782907200`). The SDK sends Unix seconds with millisecond precision. A date only or an RFC 3339 value without an offset returns `nozle: event: timestamp must be RFC3339 or Unix seconds`.

## Batch and cost events

```go theme={null}
err := client.Meter(ctx, []nozle.MeterEvent{{
  CustomerID: "workspace_123", Event: "api_calls", SubscriptionID: "workspace_123_subscription",
  TransactionID: "request_0183f", Metadata: map[string]any{"tokens": 1500},
}})
_ = err
```

`TrackCostEvent` records a Cost Event in Engine. Set `CostMeterCode` and either `ParentTransactionID` or `ExternalCustomerID`. Supply a stable `CostEventID` for retries. `CostEventParams.Timestamp` is a `*float64` of Unix seconds, unlike `UsageParams.Timestamp`, which is a string.

`nozle.CreateTransactionID()` and `nozle.CreateCostEventID()` create UUIDv7 identifiers for those two fields. Create an ID once per logical event and keep it for retries.

For high-volume workloads, enqueue the business event and call `Track` from a durable worker. Use `TrackUsage` when an operation must atomically enforce product credits.


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