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

# Custom events

> Track meaningful application outcomes with stable names and safe properties.

Use custom events for meaningful application outcomes outside the ecommerce catalog. `track()` accepts known Bily event names and your own non-empty string names.

## Give every outcome one stable name

Use snake\_case for application events:

* `user_signed_in`
* `workspace_created`
* `integration_connected`
* `contacts_imported`
* `campaign_launched`
* `billing_checkout_started`
* `billing_checkout_completed`
* `product_error_encountered`

Keep IDs, timestamps, and translated labels out of event names. Put changing values in the payload.

## Track the result, not the attempt

```typescript workspace.ts theme={null}
import { track } from "@bilyai/js";

const workspace = await createWorkspace({ name: "North America" });

track("workspace_created", {
  workspace_id: workspace.id,
  workspace_name: workspace.name,
  plan: "growth",
});
```

Send a completion event only after the operation succeeds. If a failure is useful and safe to analyze, give it a separate event.

```typescript campaign.ts theme={null}
import { track } from "@bilyai/js";

try {
  const campaign = await launchCampaign();
  track("campaign_launched", { campaign_id: campaign.id });
} catch (error) {
  track("product_error_encountered", {
    operation: "campaign_launch",
    error_code: getSafeErrorCode(error),
  });
  throw error;
}
```

Never include raw error objects. They can expose request data, stack traces, or customer content.

## Correlate one logical outcome

The SDK creates `event_id` and `ts` when you omit them. Provide a stable event ID when more than one system can report the same business outcome.

```typescript theme={null}
track("billing_checkout_completed", {
  event_id: "checkout_session_01J2M8YQ7B",
  checkout_id: "checkout_123",
  plan: "growth",
});
```

Use one `event_id` for one logical outcome. Never reuse it for a later outcome.

## Track while Bily loads

Call `track()` immediately after `init()` if needed. The SDK keeps calls while Bily loads and delivers them in order when ready.

<CardGroup cols={2}>
  <Card title="track() reference" icon="function" href="/api-reference/track" />

  <Card title="Data safety" icon="shield" href="/privacy/data-safety" />
</CardGroup>
