# Event Tracking

This guide covers how to define and send business events from your website to Treasure AI. Well-structured events are the foundation for In-Browser Message targeting — they determine when campaigns are triggered and what personalization attributes are available.

Responsibility boundary
**You define**: business events (`view_item`, `purchase`, etc.) and authenticated user IDs.

**The SDK handles automatically**: page views, session tracking, device/browser properties, message impression and click tracking (current and future).

Future SDK versions will also auto-track page views and SPA route changes — the manual patterns in this page will become optional.

## Event Architecture

All events — page views, business events, and future SDK auto-tracked events — are sent to a **single table**. Use `event_name` to distinguish event types in queries.

| **What happened**  | **Table**  | **Used for**  |
|  --- | --- | --- |
| Page view, session start/end | `events` (`event_name: 'page_view'`) | RT entry criteria, audience segmentation |
| Product view, cart, purchase | `events` (`event_name: 'view_item'`, `'add_to_cart'`, etc.) | RT attributes, trigger conditions |
| Message shown / clicked / dismissed | `events` (`event_name: 'td_msg_shown'`, etc.) — future, automatic | Campaign reporting, frequency capping |


**Count page views**: `SELECT count(*) FROM events WHERE event_name = 'page_view'`

## GA4-Compatible Event Naming

We recommend adopting **GA4 standard event names** for business events. This minimizes implementation cost for teams already using GA4, allows GTM dataLayer reuse, and provides a consistent schema for RT 2.0 targeting.

### Standard e-commerce events

| **Event name**  | **When to fire**  | **Key properties**  |
|  --- | --- | --- |
| `view_item` | User views a product detail page | `item_id`, `item_name`, `price`, `item_category`, `item_brand` |
| `add_to_cart` | User adds a product to the cart | `item_id`, `item_name`, `price`, `quantity` |
| `view_cart` | User opens the cart | `cart_total`, `item_count`, `items` (JSON string) |
| `purchase` | Order confirmation page | `transaction_id`, `revenue`, `item_count`, `item_id`, `items` |
| `add_to_wishlist` | User saves a product | `item_id`, `price` |
| `login` | User logs in | `method` (e.g., `"email"`, `"google"`) |
| `sign_up` | User registers | `method` |


### Property naming conventions

| **Category**  | **Property name**  | **Type**  | **Notes**  |
|  --- | --- | --- | --- |
| Product ID | `item_id` | string | GA4 compatible |
| Product name | `item_name` | string |  |
| Unit price | `price` | number |  |
| Order revenue | `revenue` | number |  |
| Quantity | `quantity` | number |  |
| Category (up to 5 levels) | `item_category` – `item_category5` | string | GA4 compatible |
| Brand | `item_brand` | string |  |
| Order ID | `transaction_id` | string | GA4 compatible |
| Product array | `items` | string (JSON) | See array handling below |


## Sending Events

### Basic event

```javascript
td.trackEvent('events', {
  item_id: 'SKU-001',
  item_name: 'Essential Crew Tee',
  price: 29,
  item_category: 'tops',
  item_brand: 'BASIQ',
  event_name: 'view_item',
})
```

### Cart event

```javascript
// Add to cart
td.trackEvent('events', {
  event_name: 'add_to_cart',
  item_id: 'SKU-001',
  item_name: 'Essential Crew Tee',
  price: 29,
  quantity: 1,
  item_category: 'tops',
})

// Cart view (aggregate)
td.trackEvent('events', {
  event_name: 'view_cart',
  cart_total: 87,
  item_count: 3,
  items: JSON.stringify([
    { item_id: 'SKU-001', price: 29, quantity: 1 },
    { item_id: 'SKU-014', price: 58, quantity: 2 },
  ]),
})
```

### Purchase event

```javascript
td.trackEvent('events', {
  event_name: 'purchase',
  transaction_id: 'ORDER-10432',
  revenue: 87,
  item_count: 3,
  // Flat fields from the primary item — easy to use in RT triggers and attributes
  item_id: 'SKU-001',
  item_category: 'tops',
  // Full product array as JSON string for completeness
  items: JSON.stringify([
    { item_id: 'SKU-001', item_name: 'Essential Crew Tee', price: 29, quantity: 1 },
    { item_id: 'SKU-014', item_name: 'Wide-leg Pants', price: 58, quantity: 2 },
  ]),
})
```

### Handling array data (items)

Events with multiple products (cart, purchase) require a strategy for the product list:

| **Approach**  | **Pros**  | **Cons**  |
|  --- | --- | --- |
| JSON-stringify (`items: "[{...}]"`) | Simple to implement; GTM-friendly | TD queries become complex; cannot use directly in RT attributes |
| Flat fields (`item_id_0`, `item_id_1`, ...) | Easy to query | Field count explodes with cart size |
| **Recommended: flat primary + JSON full list** | Best of both worlds for triggers + completeness | Slightly more implementation |


**Use the recommended pattern**: send the properties most useful for trigger conditions as flat fields (e.g., `item_category`, `price`), and include the complete list as a JSON string for reporting.

```javascript
td.trackEvent('events', {
  event_name: 'purchase',
  transaction_id: 'ORDER-001',
  revenue: 8760,
  item_count: 2,
  // Flat: primary item fields (easy for RT attribute conditions)
  item_id: 'SKU-123',
  item_category: 'electronics',
  price: 4380,
  // Full list as JSON string (for reporting)
  items: JSON.stringify([
    { item_id: 'SKU-123', item_name: 'Headphones', price: 4380, quantity: 1 },
    { item_id: 'SKU-456', item_name: 'Case', price: 4380, quantity: 1 },
  ]),
})
```

## Firing Events Before the SDK Loads

On pages where the SDK script loads asynchronously, events fired before the SDK is ready could be dropped. Use the **command queue pattern** to safely fire events at any time — they are replayed once the SDK is ready.

```javascript
// Safe to call at any time — queued if SDK not yet loaded
;(window.__tdEventQueue = window.__tdEventQueue || []).push([
  'trackEvent', 'events', { event_name: 'view_item', item_id: 'SKU-001', price: 29 }
])

// In your SDK initialization (sdk-loader.js or equivalent):
;(function drainQueue() {
  var queue = window.__tdEventQueue || []
  window.__tdEventQueue = { push: function(entry) {
    if (!entry || !entry.length) return
    var method = entry[0]
    if (window.td && typeof window.td[method] === 'function') {
      window.td[method].apply(window.td, entry.slice(1))
    }
  }}
  queue.forEach(window.__tdEventQueue.push)
})()
```

## GTM Integration

### Using the GA4 dataLayer

If your site already sends GA4 events via GTM, you can intercept the dataLayer and forward events to TD with minimal additional code.

```javascript
// GTM Custom HTML Tag — fires on All Pages
<script>
;(function () {
  var _push = window.dataLayer.push.bind(window.dataLayer)
  window.dataLayer.push = function (event) {
    _push(event)
    // Forward GA4-style events to TD
    if (event && event.event && window.td) {
      var GA4_EVENTS = [
        'view_item', 'add_to_cart', 'view_cart',
        'purchase', 'add_to_wishlist', 'login', 'sign_up'
      ]
      if (GA4_EVENTS.indexOf(event.event) !== -1) {
        var props = Object.assign({}, event)
        props.event_name = props.event   // preserve as event_name field
        delete props.event
        delete props.gtm  // remove GTM internals
        window.td.trackEvent('events', props)
      }
    }
  }
})()
</script>
```

GTM global variable
The TD Web SDK exposes `window.td` (or `window.Treasure`) as a global, so GTM Custom HTML tags can call `window.td.trackEvent(...)` directly without needing additional setup.

### Separate GTM tag per event

Alternatively, create a GTM **Custom HTML** tag for each event type, triggered by the corresponding GA4 event or custom GTM trigger:

```html
<!-- GTM tag: fires on GA4 "view_item" event -->
<script>
  if (window.td && {{ecommerce.items}}) {
    var item = {{ecommerce.items}}[0] || {}
    window.td.trackEvent('events', {
      event_name: 'view_item',
      item_id: item.item_id,
      item_name: item.item_name,
      price: item.price,
      item_category: item.item_category,
    })
  }
</script>
```

## Using Events in RT 2.0

Once events are flowing into your TD database, they become available in RT 2.0 for:

| **Feature**  | **How events are used**  |
|  --- | --- |
| **RT Attributes** | Compute per-user attributes from event history (e.g., `most_recent_product`, `total_spend`). These feed into Entry Criteria and audience segmentation. |
| **Entry Criteria** | Trigger a personalization section when specific event conditions are met (e.g., `item_category == "electronics"`). Currently supports event name matching (L1); property-based filtering (L2) is planned. |
| **Audience Segments** | Build batch segments from event history (e.g., "users who purchased in the last 30 days") for use in campaign targeting. |


## What the SDK Will Auto-Track in the Future

The following events currently require manual implementation. Future SDK versions will capture them automatically — the table and field names are defined in the SDK Event Requirements specification.

| **Event**  | **Current**  | **Future (SDK auto)**  |
|  --- | --- | --- |
| Page view | Manual `trackEvent('events', { event_name: 'page_view' })` call required on every route change | Automatic on page load and SPA route change |
| Session start / end | Not available | Automatic (30-min timeout, `td_session_id` attached to all events) |
| First visit | Not available | Automatic on first page load (`td_first_visit` event) |
| Message impression | Not available | Automatic when SDK renders a popup or inline message (`td_msg_shown`) |
| Message click | Not available | Automatic on CTA interaction (`td_msg_clicked`) |
| Message dismiss | Not available | Automatic on close (`td_msg_dismissed`) |


Future-proof your implementation
Using GA4-compatible event names and the flat-field pattern described in this guide means your custom business events will work with future RT 2.0 trigger condition features (L2 property matching) without schema changes.