Conversion Tracking
Upsurge measures conversions at two levels:
| Level | Source | Used for |
|---|---|---|
| Browser-observed | The SDK's conversion event (purchase by default) | Real-time activity, campaign and experiment reporting, the Conversions page |
| Authoritative | Orders sent from your store or server | Confirmed revenue, refunds and cancellations, customer value, and partner reconciliation |
Send both when you can. The browser event is fast but can be blocked or lost. The server record is the source of truth for money.
1. Choose the conversion event
The SDK marks exactly one event type as the conversion. Set it in init:
window.UpsurgeQueue.push([
'init',
{
siteId: 'site_replace_me',
baseUrl: 'https://api.upsur.ge',
conversionEventType: 'purchase', // or 'checkout_complete'
},
]);
Every event of that type gets is_conversion: true and conversion_event_type. The SDK also attaches the shopper's experiment and partner-experiment assignments so experiment results can count the conversion. Credit to overlays, recommendations, and chatbots comes from the signed product touches those runtimes record, not from the purchase payload.
Use checkout_complete only when your storefront cannot see the order confirmation page. In that case each checkout_complete must carry a stable cart_id, checkout_id, transaction_id, or order_id, or at least one item with a product_id.
2. Track the purchase in the browser
Call trackPurchase once on the order confirmation page:
window.upsurge.trackPurchase({
transaction_id: 'ORDER-10492',
total_amount: 258.0,
currency: 'USD',
coupon_code: 'SAVE15',
products: [
{
product_id: 'SKU-TRAIL-123',
product_name: 'Trail Runner',
quantity: 2,
price: 129.0,
currency: 'USD',
},
],
});
transaction_id,total_amount,currency, andproductsare required.- Use the same order ID that your store and server use, so duplicates are removed and the browser event can be matched to the authoritative order.
- A page reload that repeats the call is deduplicated by the order ID.
Track the steps before the purchase too (trackProductView, trackAddToCart, trackCheckoutStart) so funnels and cart-recovery campaigns have data. See Event tracking.
The Upsurge Shopify app's Web Pixel and the Upsurge WordPress plugin send storefront commerce events for you, and the store connection supplies orders. Use the calls on this page for custom or headless storefronts, or to add events those integrations do not send.
3. Carry the journey token to the order (optional)
With revenue attribution enabled, the SDK keeps a signed journey token that links the browser journey to the order your server records later.
window.UpsurgeQueue.push([
'init',
{
siteId: 'site_replace_me',
baseUrl: 'https://api.upsur.ge',
revenueAttribution: {
enabled: true,
consent: { analytics: false, advertising: false },
},
},
]);
// When the visitor's consent changes:
window.upsurge.setAttributionConsent({ analytics: true, advertising: true });
// Before checkout, store the token on the cart or order:
const journeyToken = window.upsurge.getJourneyToken();
Save the token as a cart attribute or order note, then send it as journey_token in the server conversion below. Consent is denied by default; without analytics consent only session-scoped state is kept.
4. Send authoritative orders from your server
POST /api/v1/commerce/conversions records the order, and later its refunds and cancellations, as authoritative. It requires a secret API key with the commerce:convert permission; create one in Sites and API keys. Never call it from the browser.
- Purchase
- Refund
- Cancellation
curl --request POST 'https://api.upsur.ge/api/v1/commerce/conversions' \
--header "X-API-Key: $UPSURGE_SECRET_KEY" \
--header 'Content-Type: application/json' \
--data '{
"schema_version": 1,
"event_type": "purchase",
"idempotency_key": "order-10492-purchase",
"website_id": "site_replace_me",
"order_id": "ORDER-10492",
"occurred_at": "2026-09-24T18:04:11Z",
"source": "trusted_server",
"currency": "USD",
"revenue_minor": 25800,
"margin_minor": 9100,
"journey_token": "JOURNEY_TOKEN_FROM_CART",
"subject": { "merchant_user_id": "customer_48291" }
}'
{
"schema_version": 1,
"event_type": "adjustment",
"idempotency_key": "order-10492-refund-1",
"website_id": "site_replace_me",
"order_id": "ORDER-10492",
"adjustment_id": "refund-1",
"occurred_at": "2026-09-27T10:00:00Z",
"source": "trusted_server",
"revenue_delta_minor": -12900,
"reason": "partial_refund"
}
{
"schema_version": 1,
"event_type": "cancellation",
"idempotency_key": "order-10492-cancel",
"website_id": "site_replace_me",
"order_id": "ORDER-10492",
"cancellation_id": "cancel-1",
"occurred_at": "2026-09-25T09:00:00Z",
"source": "trusted_server",
"reason": "customer_request"
}
| Field | Notes |
|---|---|
schema_version | Always 1. |
idempotency_key | 8–255 characters, unique per event. Retrying with the same key returns 200 instead of creating a duplicate. |
order_id | The same order ID the browser sent as transaction_id. |
occurred_at, source_updated_at | ISO 8601 timestamps with an offset. source_updated_at cannot be earlier than occurred_at. |
source | shopify, woocommerce, bigcommerce, magento, trusted_server, partner, or historical_import. |
revenue_minor, margin_minor | Integer amounts in the currency's minor unit, such as cents. Margin is optional. |
revenue_delta_minor | Signed change for an adjustment. reason is refund, partial_refund, correction, chargeback, or other. |
subject | Optional: merchant_user_id and/or a SHA-256 hashed_email (64 lowercase hex characters). Never send a raw email. |
A new record returns 201; a duplicate returns 200. Unknown fields are rejected.
For AWIN and CJ partner orders, use /partner-attribution/conversions instead.
5. Verify
- Complete a test order on the storefront.
- In the browser Network panel, confirm the
events/batchrequest includes thepurchaseevent withis_conversion: true. - Open Dashboard → Conversions and find the order under the Web source.
- If you send server conversions, confirm the API returned
201, then check that the customer's Confirmed revenue includes the order in Customers.
Troubleshooting
| Symptom | Check |
|---|---|
| Purchase recorded but not counted as a conversion | conversionEventType matches the event you send. |
400 on checkout_complete | The event needs a cart, checkout, transaction, or order ID, or an item with product_id. |
| Conversion appears twice | The browser and server must use the same order ID; the server must reuse idempotency_key on retries. |
Server call returns 401 or 403 | Use a secret key with commerce:convert in the X-API-Key header, sent from a server. |
getJourneyToken() returns undefined | Revenue attribution is not enabled, or no token has been issued yet for this journey. |