# Track Events

Use `track` to record product actions such as signups, purchases, feature usage, page views, and activation milestones.

Track a small set of meaningful events first. A few stable event names with useful properties are easier to query than a large stream of low-signal UI clicks.

## When to call it

Use `track` when something happened in your product and you want it available for segmentation, retention, funnel analysis, SQL, or AI agents.

Good first events often describe:

- activation moments
- conversion steps
- recurring feature usage
- billing or account lifecycle changes

## SDK examples

Use `track()` where the product action happens:

### TypeScript

```typescript
altertable.track('checkout_completed', {
  revenue: 49.99,
  plan: 'pro',
  currency: 'USD',
});
```

### React

```tsx
function CheckoutButton() {
  const { track } = useAltertable();

  function handleCheckout() {
    track('checkout_completed', {
      revenue: 49.99,
      plan: 'pro',
      currency: 'USD',
    });
  }

  return <button onClick={handleCheckout}>Complete checkout</button>;
}
```

### Python

```python
client.track(
    event="checkout_completed",
    distinct_id="user_abc123",
    options={
        "properties": {
            "revenue": 49.99,
            "plan": "pro",
            "currency": "USD",
        }
    },
)
```

### Ruby

```ruby
Altertable.track('checkout_completed', 'user_abc123', properties: {
  revenue: 49.99,
  plan: 'pro',
  currency: 'USD'
})
```

### Swift

```swift
client.track(
    event: "checkout_completed",
    properties: [
        "revenue": 49.99,
        "plan": "pro",
        "currency": "USD"
    ]
)
```

### Kotlin

```kotlin
Altertable.shared?.track(
    event = "checkout_completed",
    properties = mapOf(
        "revenue" to 49.99,
        "plan" to "pro",
        "currency" to "USD"
    )
)
```

`distinct_id` ties an event to a person, device, or session. Client-side SDKs can manage anonymous IDs automatically. Server-side SDKs and direct API clients should pass a stable ID on each call.

Call [`identify()`](/docs/ingest-data/product-analytics/identify.md) when you know who the user is, so anonymous and authenticated activity can be analyzed together.

## Batch events

Batching reduces request overhead when you track several events. JavaScript, React, Swift, and Kotlin batch queued calls automatically. Ruby accepts an array in one `track` call. Python sends one request per call; for an explicit batch, send an array to [`POST /track`](/docs/api-references/product-analytics.md#post-track).

### TypeScript

```typescript
altertable.track('signup', { plan: 'pro' });
altertable.track('login');
```

### React

```tsx
function SignupButton() {
  const { track } = useAltertable();

  function handleSignup() {
    track('signup', { plan: 'pro' });
    track('welcome_seen');
  }

  return <button onClick={handleSignup}>Sign up</button>;
}
```

### Python

```python
# The Python SDK has no bulk track method.
client.track("signup", "user_1", options={"properties": {"plan": "pro"}})
client.track("login", "user_2")
```

### Ruby

```ruby
Altertable.track([
  { event: 'signup', distinct_id: 'user_1', properties: { plan: 'pro' } },
  { event: 'login', distinct_id: 'user_2' }
])
```

### Swift

```swift
client.track(event: "signup", properties: ["plan": "pro"])
client.track(event: "login")
```

### Kotlin

```kotlin
Altertable.shared?.track(event = "signup", properties = mapOf("plan" to "pro"))
Altertable.shared?.track(event = "login")
```

## Transform events

Where supported, add application context before events are sent. These examples label the product area from the page URL or screen name and record the surface that produced the event:

### TypeScript

```typescript
import { altertable } from '@altertable/altertable-js';

altertable.init('{{API_KEY}}', {
  transformEvent(event) {
    const url = event.properties.$url;
    let pathname = globalThis.location?.pathname ?? '/';
    if (typeof url === 'string') {
      try {
        pathname = new URL(url, globalThis.location?.origin).pathname;
      } catch {
        pathname = globalThis.location?.pathname ?? '/';
      }
    }

    const title = globalThis.document?.title;
    const properties = { ...event.properties, surface: 'website' };

    if (pathname === '/shop' || pathname.startsWith('/shop/')) {
      properties.product = 'shop';
    }
    if (title) {
      properties.title = title;
    }

    return { ...event, properties };
  },
});
```

### React

```tsx
import { altertable } from '@altertable/altertable-js';
import { AltertableProvider } from '@altertable/altertable-react';

altertable.init('{{API_KEY}}', {
  transformEvent(event) {
    const url = event.properties.$url;
    let pathname = globalThis.location?.pathname ?? '/';
    if (typeof url === 'string') {
      try {
        pathname = new URL(url, globalThis.location?.origin).pathname;
      } catch {
        pathname = globalThis.location?.pathname ?? '/';
      }
    }

    const title = globalThis.document?.title;
    const properties = { ...event.properties, surface: 'website' };

    if (pathname === '/shop' || pathname.startsWith('/shop/')) {
      properties.product = 'shop';
    }
    if (title) {
      properties.title = title;
    }

    return { ...event, properties };
  },
});

function App() {
  return (
    <AltertableProvider client={altertable}>
      <CheckoutPage />
    </AltertableProvider>
  );
}
```

### Python

```python
# The Python SDK does not support transforming events before delivery.
# Set or filter properties in your application before calling client.track().
```

### Ruby

```ruby
# The Ruby SDK does not support transforming events before delivery.
# Set or filter properties in your application before calling Altertable.track.
```

### Swift

```swift
// The Swift SDK does not support transforming events before delivery.
// Set or filter properties in your app before calling client.track().
```

### Kotlin

```kotlin
import ai.altertable.sdk.Altertable
import ai.altertable.sdk.Event

Altertable.setup {
    apiKey = "{{API_KEY}}"
    beforeSend = listOf { event ->
        if (event is Event.Track) {
            val screen = event.properties["\$screen_name"] as? String
            val product = if (screen?.startsWith("Shop") == true) mapOf("product" to "shop") else emptyMap()
            event.copy(properties = event.properties + product + ("surface" to "app"))
        } else {
            event
        }
    }
}
```

In JavaScript and React, `transformEvent` runs on track and page events after the SDK adds context and identifiers. It does not run on identify, alias, or events forwarded from [PostHog](/docs/ingest-data/integrations/posthog.md). Configure the shared JavaScript client before rendering `<AltertableProvider>` in React.

## API reference

For HTTP request details, batching behavior, response shape, and error codes, see [`POST /track`](/docs/api-references/product-analytics.md#post-track).

## Best practices

- Use clear event names and keep them stable over time.
- Track meaningful product actions, not every low-level interaction.
- Include only properties you plan to filter, group, or query.
- Avoid secrets and regulated sensitive data in event properties.