Skip to content
Tolinku
Tolinku
Sign In Start Free
Engineering · · 9 min read

Integrating E-Commerce Event Tracking Across Five Platforms

By Tolinku Staff
|
Tolinku cross platform dashboard screenshot for engineering blog posts

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

  1. Auto-generated on first product_added: When the user adds their first item, the SDK generates a unique cart ID (e.g., cart_a1b2c3d4)
  2. Persisted across sessions: The cart ID is stored locally (localStorage on web, UserDefaults/SharedPreferences on mobile) so it survives app restarts
  3. Attached to all cart events: Every product_added, product_removed, cart_viewed, checkout_started, and purchase event includes this cart ID
  4. Cleared after purchase: When a purchase completes, the cart ID is cleared. The next product_added generates a new one
  5. 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, not 89.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.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.