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
Event Method When to Track Items Served trackItemsServed() / trackSearch() / trackRecommendations()Products displayed to user Click context.trackClick()User clicks a product View context.trackView()Product becomes visible (50%+ in viewport for 1s) Add to Cart context.trackAddToCart()User adds product to cart Purchase trackPurchase()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)
Track when a user clicks on a product. position is the 0-indexed position in the results.
trackView(itemId, position)
Track when a product becomes visible. Call when 50%+ of the product is visible for 1+ second.
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
Tracking Events Detailed tracking implementation
Conversion Tracking Purchase and conversion attribution
Auto Track Plugin Automatic event tracking