E-commerce event tracking gives you visibility into how users interact with your products: what they view, what they add to cart, what they buy, and what they return. Tolinku's SDKs support 13 e-commerce event types across five platforms (Web, React Native, iOS, Android, Flutter), with consistent APIs and automatic features like cart ID management, event batching, and background flushing.
This guide covers the event types, when to fire each one, and platform-specific integration code. If you are starting from scratch, begin with the SDK documentation. For the backend API, see the e-commerce API reference.
The 13 E-Commerce Event Types
Each event type captures a specific step in the shopping journey. Fire them at the right moments to build a complete picture of your e-commerce funnel.
| Event | When to Fire | Required Fields |
|---|---|---|
product_viewed |
User views a product detail page | item_id, name, price, currency |
product_list_viewed |
User views a category or search results page | list_name, items[] |
product_clicked |
User taps a product from a list | item_id, name, list_name |
product_added |
User adds an item to their cart | item_id, name, price, currency, quantity |
product_removed |
User removes an item from their cart | item_id, quantity |
cart_viewed |
User views their cart | items[], cart_total, currency |
checkout_started |
User begins the checkout flow | items[], cart_total, currency |
checkout_step_completed |
User completes a checkout step (shipping, payment, etc.) | step_number, step_name |
payment_info_entered |
User enters payment information | payment_method |
purchase |
Order is confirmed | order_id, total, currency, items[] |
refund |
A refund is processed | order_id, total, currency |
coupon_applied |
User applies a discount code | coupon_code, discount_amount, currency |
coupon_removed |
User removes a discount code | coupon_code |
These event names follow the conventions established by Google's GA4 e-commerce events specification, so they will be familiar to most analytics teams.
The Purchase Event in Detail
The purchase event is the most important event in your e-commerce tracking. It closes the funnel and generates revenue data. Here is the complete field specification.
Required Fields
| Field | Type | Description |
|---|---|---|
| order_id | string | Unique order identifier (must be unique across your system) |
| total | number | Order total in the currency's smallest unit (e.g., 8999 = $89.99) |
| currency | string | ISO 4217 currency code (e.g., "USD", "JPY", "EUR") |
| items | array | Array of purchased item objects |
Optional Fields
| Field | Type | Description |
|---|---|---|
| subtotal | number | Order subtotal before tax and shipping |
| tax | number | Tax amount |
| shipping | number | Shipping cost |
| discount | number | Discount amount applied |
| coupon_code | string | Coupon or promo code used |
| payment_method | string | Payment method (e.g., "credit_card", "apple_pay") |
Item Object
Each item in the items array contains:
| Field | Type | Required | Description |
|---|---|---|---|
| item_id | string | Yes | SKU or product ID |
| name | string | Yes | Product name |
| price | number | Yes | Unit price in smallest currency unit |
| quantity | number | Yes | Quantity purchased |
| category | string | No | Product category |
| brand | string | No | Product brand |
| variant | string | No | Product variant (e.g., "Blue / Large") |
SDK Setup by Platform
Web (JavaScript/TypeScript)
Install the SDK:
npm install @tolinku/web-sdk
Initialize and track events:
import { Tolinku } from '@tolinku/web-sdk';
const tolinku = new Tolinku({ apiKey: 'tolk_pub_your_key' });
tolinku.setUserId('usr_789');
// Track a product view
await tolinku.ecommerce.viewItem({
items: [{ item_id: 'SKU-001', item_name: 'Premium Widget', price: 49.99, item_category: 'widgets' }]
});
// Track adding to cart
await tolinku.ecommerce.addToCart({
items: [{ item_id: 'SKU-001', item_name: 'Premium Widget', price: 49.99, quantity: 1 }]
});
// Track a purchase
await tolinku.ecommerce.purchase({
transaction_id: 'ORD-2026-1234',
revenue: 54.98,
currency: 'USD',
tax: 4.99,
items: [
{ item_id: 'SKU-001', item_name: 'Premium Widget', price: 49.99, quantity: 1, item_category: 'widgets' }
]
});
Events are batched automatically. On page unload, navigator.sendBeacon ensures queued events are delivered. Cart IDs are managed via sessionStorage.
React Native
npm install @tolinku/react-native-sdk @react-native-async-storage/async-storage
import { Tolinku } from '@tolinku/react-native-sdk';
Tolinku.init({ apiKey: 'tolk_pub_your_key' });
Tolinku.setUserId('usr_789');
// Track a product view
await Tolinku.ecommerce.viewItem({
items: [{ item_id: 'SKU-001', item_name: 'Premium Widget', price: 49.99 }]
});
// Track a purchase
await Tolinku.ecommerce.purchase({
transaction_id: 'ORD-2026-1234',
revenue: 54.98,
currency: 'USD',
items: [{ item_id: 'SKU-001', item_name: 'Premium Widget', price: 49.99, quantity: 1 }]
});
// Force flush
await Tolinku.ecommerce.flush();
The React Native SDK auto-flushes when the app enters the background via AppState. Cart IDs are persisted in AsyncStorage.
iOS (Swift)
Add via Swift Package Manager: https://github.com/tolinku/ios-sdk
import TolinkuSDK
try Tolinku.configure(apiKey: "tolk_pub_your_key")
let tolinku = try Tolinku.requireShared()
tolinku.setUserId("usr_789")
// Track a product view
await tolinku.ecommerce.viewItem(
items: [TolinkuItem(itemId: "SKU-001", itemName: "Premium Widget", price: 49.99, itemCategory: "widgets")]
)
// Track a purchase
await tolinku.ecommerce.purchase(
transactionId: "ORD-2026-1234",
revenue: 54.98,
currency: "USD",
tax: 4.99,
items: [TolinkuItem(itemId: "SKU-001", itemName: "Premium Widget", price: 49.99, quantity: 1)]
)
Money values use Swift Decimal for precision. Cart IDs are persisted in UserDefaults. The SDK auto-flushes when the app enters the background. For iOS deep link setup, see Apple's Universal Links documentation.
Android (Kotlin)
Add via Gradle:
implementation("com.tolinku:sdk:0.1.0")
import com.tolinku.sdk.Tolinku
import com.tolinku.sdk.TolinkuItem
Tolinku.configure(apiKey = "tolk_pub_your_key", context = applicationContext)
Tolinku.setUserId("usr_789")
// Track a product view
Tolinku.ecommerce.viewItem(
items = listOf(TolinkuItem(itemId = "SKU-001", itemName = "Premium Widget", price = BigDecimal("49.99")))
)
// Track a purchase
Tolinku.ecommerce.purchase(
transactionId = "ORD-2026-1234",
revenue = BigDecimal("54.98"),
currency = "USD",
tax = BigDecimal("4.99"),
items = listOf(TolinkuItem(itemId = "SKU-001", itemName = "Premium Widget", price = BigDecimal("49.99"), quantity = 1))
)
Money values use BigDecimal for precision. Cart IDs are persisted in SharedPreferences. The SDK auto-flushes via ActivityLifecycleCallbacks. For Android deep link setup, see Google's App Links documentation.
Flutter (Dart)
Add to pubspec.yaml:
dependencies:
tolinku: ^0.1.0
import 'package:tolinku/tolinku.dart';
Tolinku.configure(apiKey: 'tolk_pub_your_key');
Tolinku.instance.setUserId('usr_789');
// Track a product view
await Tolinku.instance.ecommerce.viewItem(
items: [TolinkuItem(itemId: 'SKU-001', itemName: 'Premium Widget', price: 49.99, itemCategory: 'widgets')],
);
// Track a purchase
await Tolinku.instance.ecommerce.purchase(
transactionId: 'ORD-2026-1234',
revenue: 54.98,
currency: 'USD',
tax: 4.99,
items: [TolinkuItem(itemId: 'SKU-001', itemName: 'Premium Widget', price: 49.99, quantity: 1)],
);
Cart IDs are persisted in SharedPreferences. For lifecycle-aware flushing, add Tolinku.instance.ecommerce.flush() to your WidgetsBindingObserver.didChangeAppLifecycleState handler.
Cart ID Lifecycle
The SDK automatically manages a cart ID to group cart-related events together. Understanding the lifecycle prevents common tracking errors.
How It Works
- Auto-generated on first
product_added: When the user adds their first item, the SDK generates a unique cart ID (e.g.,cart_a1b2c3d4) - Persisted across sessions: The cart ID is stored locally (localStorage on web, UserDefaults/SharedPreferences on mobile) so it survives app restarts
- Attached to all cart events: Every
product_added,product_removed,cart_viewed,checkout_started, andpurchaseevent includes this cart ID - Cleared after
purchase: When a purchase completes, the cart ID is cleared. The nextproduct_addedgenerates a new one - Cleared after timeout: If no cart activity occurs for the configured timeout (default: 30 minutes), the cart is marked as abandoned and the ID is eventually cleared
Why This Matters
The cart ID links together the full sequence of events from first "add to cart" to purchase (or abandonment). Without it, you cannot:
- Calculate cart abandonment rates accurately
- Track how many items were added/removed before purchase
- Associate a specific cart with a recovery campaign
- Measure time-to-purchase from first cart interaction
Event Batching
The SDK does not send each event individually. It batches events to reduce network overhead and improve performance.
Batching Rules
| Trigger | Condition |
|---|---|
| Batch size | 10 events accumulated |
| Timer | 5 seconds since the last flush |
| App lifecycle | App goes to background (mobile) or page unloads (web) |
| Manual flush | Calling tolinku.flush() |
Whichever condition is met first triggers the flush. For most apps, the 5-second timer is the primary trigger during active use, and the lifecycle event catches anything remaining when the user leaves.
Why Batching Matters
Without batching, an active shopping session (view product, add to cart, view another product, add to cart, view cart, start checkout) would generate 6 individual HTTP requests in a few seconds. Batching reduces this to 1 request containing 6 events. This is better for battery life, network usage, and server load.
Background Flushing
What happens when the user leaves mid-session? Unsent events need to be delivered before they are lost.
Web: sendBeacon
On web, the SDK uses the Navigator.sendBeacon() API during the visibilitychange event. sendBeacon is designed for exactly this purpose: it sends data asynchronously without delaying page unload. Unlike fetch or XMLHttpRequest, sendBeacon requests are not cancelled when the page navigates away.
// The SDK handles this automatically. You do not need to call it manually.
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
tolinku.flush(); // Uses sendBeacon internally
}
});
Mobile: AppState Listeners
On iOS, the SDK listens for UIApplication.willResignActiveNotification to flush events when the app moves to the background. On Android, it uses ProcessLifecycleOwner to detect the same transition.
React Native and Flutter use their respective platform bridges to achieve the same behavior (AppState listener in React Native, WidgetsBindingObserver in Flutter).
Persistence
If the flush fails (network error, timeout), the SDK persists unsent events locally and retries on the next app launch. Events are not lost unless the user uninstalls the app before the retry succeeds.
Error Recovery and Retry
Network requests fail. The SDK handles this with an exponential backoff retry strategy:
| Attempt | Delay | Max Events Retained |
|---|---|---|
| 1 | Immediate | N/A |
| 2 | 1 second | 1,000 |
| 3 | 5 seconds | 1,000 |
| 4 | 30 seconds | 1,000 |
| 5 | 2 minutes | 1,000 |
After 5 failed attempts, the SDK stops retrying for that batch and moves on. The 1,000-event limit prevents the local queue from consuming excessive storage on the device.
For server-side errors (5xx), the SDK retries. For client-side errors (4xx), it does not retry (the request is malformed and retrying will not help). The exception is 429 (rate limit), which is retried after the Retry-After header duration.
Testing Your Integration
1. Enable Debug Mode
All SDKs support a debug mode that logs events to the console instead of (or in addition to) sending them to the server:
// Web
const tolinku = new Tolinku({
appKey: 'tolk_pub_your_key',
debug: true // Logs events to console
});
2. Verify Event Structure
Common issues to check for:
- Amounts in smallest unit: $89.99 should be sent as
8999, not89.99 - Currency as ISO 4217: Use
"USD", not"$"or"dollars" - Unique order IDs: Each purchase must have a unique
order_id. Duplicates are deduplicated on the server. - Items array is populated: A purchase with an empty items array will be accepted but will not generate product-level analytics
3. Walk Through the Full Funnel
Track a complete shopping journey in debug mode and verify each event:
product_list_viewed → product_clicked → product_viewed →
product_added → cart_viewed → checkout_started →
checkout_step_completed (x2-3) → payment_info_entered → purchase
Verify that the cart ID is consistent across all cart-related events and that it clears after the purchase.
4. Test Edge Cases
| Scenario | Expected Behavior |
|---|---|
| User adds item, closes app, reopens | Cart ID persists, cart events continue |
| Network disconnected during purchase | Event queued locally, retried on reconnect |
| User adds then removes the same item | Both events tracked, cart state is accurate |
| Duplicate purchase event (same order_id) | Server deduplicates, counted once |
| Zero-quantity product_added | Rejected by SDK validation |
Common Mistakes
Missing User ID
If you do not set a user ID before tracking e-commerce events, the events are tracked as anonymous. This means you cannot:
- Build user-level segments
- Calculate per-user LTV
- Attribute purchases to referral campaigns
- Send user-targeted webhooks
Set the user ID as soon as the user authenticates:
tolinku.setUserId('usr_789');
Wrong Currency Format
The SDK accepts standard decimal amounts (49.99), not cents (4999). The platform handles cents conversion internally. Send the amount as your users see it:
// Correct: use the display amount
await tolinku.ecommerce.purchase({
transaction_id: 'order_123',
revenue: 89.99, // $89.99, not 8999
currency: 'USD',
items: [{ item_id: 'SKU-001', price: 89.99, quantity: 1 }]
});
For zero-decimal currencies like JPY, send the whole number:
// Correct for JPY
await tolinku.ecommerce.purchase({
transaction_id: 'order_456',
revenue: 9800, // 9,800 yen
currency: 'JPY',
items: [{ item_id: 'SKU-002', price: 9800, quantity: 1 }]
});
Forgetting to Flush
In single-page applications, the visibilitychange event handles flushing automatically. But if you are navigating with full page reloads (server-rendered pages), events tracked just before navigation may be lost. Call tolinku.flush() before programmatic navigation:
async function handlePurchase() {
await tolinku.ecommerce.purchase({ /* ... */ });
await tolinku.ecommerce.flush(); // Ensure the event is sent
window.location.href = '/order-confirmation';
}
Not Tracking the Full Funnel
Some teams only track the purchase event. This gives you revenue data but no funnel visibility. You cannot calculate:
- Product view to purchase conversion rate
- Add-to-cart rate
- Checkout abandonment rate
- Which checkout step loses the most users
Track at minimum: view_item, add_to_cart, begin_checkout, and purchase. The other events add detail but these four give you the core funnel.
Conclusion
E-commerce event tracking is the foundation for revenue analytics, audience segmentation, webhook automation, and attribution. The 13 event types cover the complete shopping journey, from product discovery through purchase and refund.
The key integration points: set the user ID early (required for attribution), use standard decimal amounts for revenue (the SDK handles cents conversion internally), track the full funnel (not just purchases), and let the SDK handle cart IDs, batching, and background flushing automatically.
For SDK reference and installation guides, see the SDK documentation. For the server-side API, see the e-commerce API reference. For what you can do with the data once it is flowing, see the e-commerce analytics features.
Get deep linking tips in your inbox
One email per week. No spam.