Skip to main content

Overview

Event tracking captures user interactions—impressions, clicks, views, adds-to-cart, and purchases. This data powers:
  • Analytics: Understand how users interact with search and recommendations
  • Attribution: Link sales back to the search or recommendation that drove them
  • Model Improvement: Feedback loops that improve recommendations over time

The ServeContext Pattern

When products are displayed, you create a ServeContext that tracks subsequent interactions:
// Products are served
const context = refine.events.trackSearch(query, results, options);

// User interacts
context.trackClick(productId, position);    // They clicked
context.trackView(productId, position);     // It became visible
context.trackAddToCart(productId);          // They added to cart
This pattern ensures all interactions are correctly attributed to the original serve event.

Quick Start

import { Refine } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789'
});

// Search and track
const results = await refine.search.text({ query: 'shoes', topK: 20 });

const searchContext = refine.events.trackSearch(
  'shoes',
  results.results,
  { surface: 'search_results', totalResults: results.totalResults }
);

// Track clicks
function handleProductClick(productId: string, position: number) {
  searchContext.trackClick(productId, position);
}

// Track purchase (separate from serve context)
refine.events.trackPurchase({
  orderId: 'order_123',
  value: 149.99,
  currency: 'USD',
  items: [{ itemId: 'sku_001', quantity: 1, unitPrice: 149.99 }]
});

Event Types

EventMethodWhen to Track
Items ServedtrackItemsServed() / trackSearch() / trackRecommendations()Products displayed to user
Clickcontext.trackClick()User clicks a product
Viewcontext.trackView()Product becomes visible (50%+ in viewport for 1s)
Add to Cartcontext.trackAddToCart()User adds product to cart
PurchasetrackPurchase()Order completed

Tracking Methods

trackSearch()

Convenience method for search results:
const searchContext = refine.events.trackSearch(
  query,           // The search query
  results.results, // Array of products
  {
    surface: 'search_results',
    totalResults: results.totalResults
  }
);

trackRecommendations()

Convenience method for recommendation results:
const recsContext = refine.events.trackRecommendations(
  recs.serveId,    // From recommendation response
  recs.results,    // Array of products
  'product_page',  // Surface
  'similar-items', // Source
  { anchorId: 'sku_001' }  // Optional metadata
);

trackItemsServed()

Low-level method for custom scenarios:
const context = refine.events.trackItemsServed({
  surface: 'home_page',
  source: 'curated',
  itemIds: ['sku_001', 'sku_002', 'sku_003'],
  totalResults: 3
});

trackPurchase()

Track completed purchases:
refine.events.trackPurchase({
  orderId: 'order_12345',
  value: 299.98,
  currency: 'USD',
  items: [
    { itemId: 'sku_001', quantity: 1, unitPrice: 199.99 },
    { itemId: 'sku_002', quantity: 2, unitPrice: 49.99 }
  ]
});

ServeContext Methods

After calling a track method, you get a context with these methods:
const context = refine.events.trackSearch(query, results, options);

// User clicked product at position 3
context.trackClick('sku_001', 3);

// Product became visible at position 0
context.trackView('sku_001', 0);

// User added product to cart
context.trackAddToCart('sku_001');
trackClick(itemId, position)
function
Track when a user clicks on a product. position is the 0-indexed position in the results.
trackView(itemId, position)
function
Track when a product becomes visible. Call when 50%+ of the product is visible for 1+ second.
trackAddToCart(itemId)
function
Track when a user adds a product to their cart from this serve context.

Event Queue Management

Events are automatically batched and sent. You can control this behavior:
// Force send all queued events immediately
await refine.events.flush();

// Pause event sending (e.g., when offline)
refine.events.pause();

// Resume event sending
refine.events.resume();

// Get queue metrics
const metrics = refine.events.getMetrics();
console.log(metrics);
// { queued: 5, sent: 142, failed: 0, retrying: 0 }

Configuration

Control batching, persistence, and retries in SDK initialization:
const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789',
  events: {
    enabled: true,
    batchSize: 10,          // Events per batch
    flushInterval: 5000,    // Max ms between sends
    maxQueueSize: 1000,     // Max events to queue
    maxRetries: 3,          // Retry attempts
    persistence: 'localStorage', // Queue storage
    sessionTimeout: 30 * 60 * 1000  // Session length
  }
});

Page Unload Handling

Ensure events are sent before the user leaves:
// Modern approach (recommended)
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    refine.events.flush();
  }
});

// Fallback for older browsers
window.addEventListener('beforeunload', () => {
  refine.events.flush();
});

Complete Example

import { Refine } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789'
});

class SearchPage {
  private searchContext: any;

  async search(query: string) {
    const results = await refine.search.text({ query, topK: 24 });

    // Track the search
    this.searchContext = refine.events.trackSearch(query, results.results, {
      surface: 'search_results',
      totalResults: results.totalResults
    });

    this.renderResults(results.results);
    this.setupViewabilityTracking();
    
    return results;
  }

  private renderResults(products: any[]) {
    const container = document.getElementById('results')!;
    
    container.innerHTML = products.map((product, index) => `
      <div class="product" data-id="${product.productId}" data-position="${index}">
        <img src="${product.imageUrl}" alt="${product.title}" />
        <h3>${product.title}</h3>
        <p>$${product.price}</p>
        <button class="add-to-cart">Add to Cart</button>
      </div>
    `).join('');

    // Click handlers
    container.querySelectorAll('.product').forEach(el => {
      const productId = (el as HTMLElement).dataset.id!;
      const position = parseInt((el as HTMLElement).dataset.position!);

      el.addEventListener('click', (e) => {
        if ((e.target as HTMLElement).classList.contains('add-to-cart')) {
          this.searchContext?.trackAddToCart(productId);
        } else {
          this.searchContext?.trackClick(productId, position);
        }
      });
    });
  }

  private setupViewabilityTracking() {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const el = entry.target as HTMLElement;
            const productId = el.dataset.id!;
            const position = parseInt(el.dataset.position!);
            
            // Track view after 1 second of visibility
            setTimeout(() => {
              if (entry.isIntersecting) {
                this.searchContext?.trackView(productId, position);
                observer.unobserve(el);
              }
            }, 1000);
          }
        });
      },
      { threshold: 0.5 }
    );

    document.querySelectorAll('.product').forEach(el => {
      observer.observe(el);
    });
  }
}

Next Steps