Integration patterns
Common integration patterns for page views, SPA navigation, cart events, purchases, and user login/logout using the Sophi Web Object SDK.
Examples use SophiTracker.push; equivalent window.sophi_object.* = ... assignments produce the same events after the SDK has started.
Static / server-rendered page
The full inline snippet (config object + pre-load stub) must appear before the async SDK tag:
<script>
window.sophi_object = {
config: {
apiKey: 'YOUR_API_KEY',
userId: 'usr-123'
},
page: {
type: 'Product',
url: 'https://store.com/products/blue-dress',
title: 'Blue Dress | Store',
language: 'tr-TR'
},
product: {
id: '1627421',
category_ids: ['cat-dresses', 'cat-womens'],
name: 'Blue Dress',
url: 'https://store.com/products/blue-dress',
product_image_url: 'https://cdn.store.com/blue.jpg',
taxonomy: ['Clothing', 'Dresses', 'Evening'],
currency: 'TRY',
unit_price: 1200.0,
unit_sale_price: 990.0,
in_stock: 1
}
};
window.SophiTracker = window.SophiTracker || (function () {
var _q = [];
var _fwd = null;
function push() {
if (_fwd) { return _fwd.apply(null, arguments); }
_q.push(Array.prototype.slice.call(arguments));
}
push._sophiQ = _q;
push._sophiSetFwd = function (fn) { _fwd = fn; };
return { push: push };
}());
</script>
<script
src="https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-object@1.3.x/dist/sophi.min.js"
async
></script>This typically emits page_view (from page) and product_view (from product), collected before any buffered push() calls are drained.
@1.3.x automatically selects the latest compatible patch within the 1.3 minor line. Never use @latest in production. See Installation for the full CDN table.
SPA route change
SophiTracker.push({
page: {
type: 'Category',
url: 'https://store.com/womens/dresses',
category_id: 'cat-dresses'
},
listing: {
category_id: 'cat-dresses',
name: "Women's Dresses",
taxonomy: ['Clothing', 'Dresses'],
total_results: 234,
page_number: 1,
page_size: 24,
items: [
{
product_id: '1627421',
position: 1,
name: 'Blue Dress',
url: 'https://store.com/products/blue-dress',
product_image_url: 'https://cdn.store.com/blue.jpg',
currency: 'TRY',
unit_price: 1200.0,
unit_sale_price: 990.0,
in_stock: 1
}
]
}
});push() calls made before the SDK is ready are automatically buffered by the stub and drained after initialisation. You do not need to check window.SophiTracker before calling it.
Add to cart
Set event_type explicitly; include product and basket.
SophiTracker.push({
event_type: 'add_to_cart',
page: {
type: 'Product',
url: 'https://store.com/products/blue-dress'
},
product: {
id: '1627421',
category_ids: ['cat-dresses'],
name: 'Blue Dress',
url: 'https://store.com/products/blue-dress',
product_image_url: 'https://cdn.store.com/blue.jpg',
taxonomy: ['Clothing', 'Dresses'],
currency: 'TRY',
unit_price: 1200.0,
unit_sale_price: 990.0,
in_stock: 1
},
basket: {
currency: 'TRY',
total: 990.0,
line_items: [
{
product_id: '1627421',
quantity: 1,
currency: 'TRY',
unit_price: 1200.0,
unit_sale_price: 990.0,
line_total: 990.0
}
]
}
});For remove_from_cart, use the same shape with an updated basket and event_type: 'remove_from_cart'.
Purchase confirmation
page on a purchase push is envelope context (where the order completed). The Web Object emits one purchase event — it does not also fire page_view.
pagemay be included in the same push, or already sit onwindow.sophi_objectfrom a prior page update.- If
pageis missing entirely, the SDK skips the purchase and warns in the console. - To track the confirmation URL as a page view, call
SophiTracker.push({ page })separately (typically from a layout/route tracker).
SophiTracker.push({
page: {
type: 'Confirmation',
url: 'https://store.com/checkout/confirmation/ORD-99231'
},
transaction: {
order_id: 'ORD-99231',
currency: 'TRY',
total: 1989.9,
subtotal: 1980.0,
tax: 0.0,
shipping_cost: 9.9,
discount_total: 210.0,
coupon_code: 'SS25',
payment_method: 'credit_card',
line_items: [
{
product_id: '1627421',
quantity: 2,
currency: 'TRY',
unit_price: 1200.0,
unit_sale_price: 990.0,
line_total: 1980.0
}
]
}
});
// Fires: one purchase (page is envelope context, not a sibling page_view)A transaction-only push is also valid when window.sophi_object.page is already set (for example by a parent route tracker):
SophiTracker.push({
transaction: { /* order fields */ }
});
// Fires: one purchase using the current sophi_object.pageUser login
SophiTracker.updateUser('usr-8842');
SophiTracker.push({
user: {
consent: {
gdpr_optin: true,
version: '2026-01-15',
timestamp: Math.floor(Date.now() / 1000)
},
identity: {
uuid: 'usr-8842',
email: 'jane@example.com'
},
profile: {
name: 'Jane',
surname: 'Doe',
gender: 'F'
},
behavior: {
returning: true,
has_transacted: true,
transaction_count: 4
}
}
});User logout
SophiTracker.updateUser(undefined);Clears session state and establishes a new anonymous session for subsequent events.
Important warnings
window.sophi_object holds named state objects (page, product, basket, …), not a list of events.
- Do not do:
window.sophi_object = window.sophi_object || [] - Assigning the same key twice before bootstrap keeps only the last value.
- Do not replace
window.sophi_objectafter the SDK Proxy is installed.
Setting an initial value in sophi_object and also calling SophiTracker.push() for the same event will produce two events. Use only one mechanism per event per page load.
Browser history navigation does not automatically trigger Sophi events. Your router must call SophiTracker.push() on every route change, including back/forward.
When storageConsent: false at startup, pre-load buffered events are discarded on bootstrap — not held for replay when setConsent(true) is later called.
Related
Validation
Before an event is sent, @usesophi/sophi-web-object runs client-side field validation; failures log a console.warn and the event is skipped without any network request.
GTM integration
How to load @usesophi/sophi-web-object (the storefront event-tracking SDK) through Google Tag Manager, including recommended split and all-in-GTM configurations.