📌 Series: GA4 × SFCC Reconciliation — Part 5 of 5
This article is part of a technical series on reconciling e-commerce order data between Salesforce Commerce Cloud (SFCC) and Google Analytics 4 (GA4). It assumes familiarity with BigQuery, LookML, and the reconciliation table architecture covered in Parts 1–4. If you are arriving here directly, the section below covers the essential context.
Context: What Is SFCC and Why Do We Compare It to GA4?
Salesforce Commerce Cloud (SFCC) is an enterprise e-commerce platform used by mid-to-large retailers to manage their online storefront, product catalogue, orders, customer accounts, and fulfilment. When a customer places an order, SFCC records it server-side — regardless of what the customer’s browser does.
Google Analytics 4 (GA4), by contrast, tracks behaviour client-side — via a JavaScript tag running in the customer’s browser. When a purchase completes, GA4 fires a purchase event from the browser, sending transaction data to Google’s servers.
In theory, both systems should record the same set of orders. In practice, they never do. GA4 systematically reports fewer orders than SFCC. The gap typically ranges from 10% to 35% of web orders.
What is a match rate?
The match rate is the percentage of SFCC orders for which a corresponding GA4 session and purchase event can be found. A match rate of 88% means that for 100 web orders in SFCC, 88 have a corresponding GA4 purchase event. The remaining 12 are invisible to your standard analytics reports.
What is an orphan order?
An orphan order is an SFCC order that has no matching GA4 session. From GA4’s perspective, it does not exist. The customer paid. The revenue is real. The attribution is simply missing.
The critical mistake is treating all orphan orders the same. Some are structurally untraceable (offline, B2B). Others are a genuine tracking failure that can — and should — be fixed.
You have normalised your transaction IDs. Your match rate is now 88% on web orders. But 12% of web orders and all offline orders still show no GA4 session. The correct approach is to classify every order in your data model, measure each category separately, and use that information to make better decisions.
The Five Categories of Unmatched Orders
Why Ignoring Unmatched Orders Creates Compounding Errors
When you exclude unmatched orders from your analysis, you do not make them disappear. You redistribute their impact across your remaining metrics in ways that are invisible and cumulative.
The “direct” inflation effect
In GA4, any session or conversion without a recognised source is attributed to “direct / none”. If 30% of your SFCC orders are unmatched and include orders where channel attribution was lost (ITP on Safari, WebView), those orders are inflating “direct” in your attribution reports. A channel that appears to drive 25% revenue via direct may actually be driving 10% direct and 15% through attribution-lost paid channels.
The ROAS underestimation cascade
User A clicks your Google Ads search ad on June 1st. They return on June 10th via Safari. ITP has erased their cookie. GA4 sees them as a new “direct” visitor. They convert. SFCC records the order. GA4 attributes it to direct. Google Ads does not receive credit. Your paid search ROAS looks lower than it is. You reduce your budget based on systematically incorrect data.
This is the structural reality for any business selling products with a consideration cycle longer than 7 days to Safari users — which in 2024 Western Europe includes 28–32% of desktop and 50–60% of mobile users.
The conversion rate distortion
If 15% of actual purchases generated no GA4 event, your conversion rate is systematically understated. Every A/B test, funnel analysis, and cohort comparison is biased downward. If you use this rate to calculate expected revenue per visitor for bidding, you are systematically underbidding.
The LookML Classification System
The solution is to classify every unmatched order before it reaches any dashboard.
Tier 1: Origin classification
dimension: order_origin {
label: "Order Origin"
type: string
sql: CASE
WHEN ${TABLE}.ga4_session_id IS NOT NULL
THEN 'tracked_web'
WHEN ${TABLE}.ga4_session_id IS NULL
AND ${TABLE}.sfcc_channel IN ('store', 'pos', 'kiosk', 'retail')
THEN 'offline_store'
WHEN ${TABLE}.ga4_session_id IS NULL
AND ${TABLE}.sfcc_channel IN ('phone', 'callcenter', 'agent', 'fax')
THEN 'offline_callcenter'
WHEN ${TABLE}.ga4_session_id IS NULL
AND ${TABLE}.sfcc_customer_type IN ('b2b', 'wholesale', 'partner', 'reseller')
THEN 'b2b_wholesale'
WHEN ${TABLE}.ga4_session_id IS NULL
AND ${TABLE}.sfcc_order_type IN ('subscription', 'renewal', 'auto_reorder')
THEN 'subscription_automated'
WHEN ${TABLE}.ga4_session_id IS NULL
AND ${TABLE}.sfcc_order_source = 'mobile_app'
THEN 'mobile_app_untracked'
WHEN ${TABLE}.ga4_session_id IS NULL
AND ${TABLE}.sfcc_channel IN ('web', 'storefront', 'website')
THEN 'dark_traffic_web'
ELSE 'unknown'
END ;;
}
Tier 2: Tracking confidence
dimension: tracking_confidence {
label: "Tracking Confidence"
type: string
sql: CASE
WHEN ${TABLE}.order_origin = 'tracked_web'
THEN '1. High'
WHEN ${TABLE}.order_origin IN ('offline_store','offline_callcenter','b2b_wholesale','subscription_automated')
THEN '2. Structural gap'
WHEN ${TABLE}.order_origin IN ('dark_traffic_web','mobile_app_untracked')
THEN '3. Fixable gap'
ELSE '4. Unknown'
END ;;
}
The Complete Metrics Library
measure: tracked_revenue {
label: "Tracked Revenue (€)"
type: sum
sql: CASE WHEN ${TABLE}.order_origin = 'tracked_web'
THEN ${TABLE}.sfcc_net_revenue END ;;
value_format_name: decimal_2
description: "Net revenue attributable to digital channels."
}
measure: fixable_gap_revenue {
label: "Fixable Gap Revenue (€)"
type: sum
sql: CASE WHEN ${TABLE}.tracking_confidence LIKE '3.%'
THEN ${TABLE}.sfcc_net_revenue END ;;
value_format_name: decimal_2
description: "Revenue from web orders with no GA4 match. Business case for tracking investment."
}
measure: web_dark_traffic_rate {
label: "Web Dark Traffic Rate"
type: number
sql: COUNT(DISTINCT CASE WHEN ${TABLE}.order_origin = 'dark_traffic_web' THEN ${TABLE}.order_id END)
/ NULLIF(COUNT(DISTINCT CASE WHEN ${TABLE}.sfcc_channel IN ('web','storefront') THEN ${TABLE}.order_id END), 0)
;;
value_format_name: percent_1
description: "Target: below 15%. Above 20% indicates tracking issues."
}
measure: revenue_coverage_ratio {
label: "Revenue Coverage Ratio"
type: number
sql: ${tracked_revenue} / NULLIF(${sfcc_net_revenue}, 0) ;;
value_format_name: percent_1
}
The Complete Reconciliation Dashboard Architecture
Dashboard 1: Executive revenue picture
KPI tiles: SFCC total net revenue, tracked revenue, structural gap revenue, fixable gap revenue, revenue coverage ratio. Five numbers that give a complete picture of revenue by attribution status.
Dashboard 2: Finance reconciliation
Line chart: SFCC net revenue vs GA4 reported revenue by month with delta. Answers the Monday morning question: “why are the numbers different?”
Dashboard 3: Tracking implementation health
Trend line: web dark traffic rate over the last 90 days. Spikes = deployments that broke something. Gradual increase = systemic degradation (consent mode drift, ad blocker growth).
Dashboard 4: Business mix by channel
Breakdown of orders and revenue by order_origin, by month. Shows the true contribution of offline, B2B, and subscription channels independent of what appears in GA4.
The Minimum Viable Starting Point
- Run the diagnostic queries from Part 3 to understand your current match rate
- Create the two normalised BigQuery views (
norm_sfcc_ordersandnorm_ga4_purchases) - Create a reconciliation table in BigQuery with order_id, order_origin, sfcc_net_revenue, ga4_revenue, revenue_delta, channel, created_at
- Connect to Looker and define your core governed measures
- Build the four KPI tiles and the dark traffic trend line
Sources and Further Reading
- Apple ITP — official Webkit documentation: webkit.org/tracking-prevention
- Safari ITP and web analytics — Simo Ahava: simoahava.com
- WebView tracking with Google Tag Manager: developers.google.com/tag-platform/tag-manager/web/webview
- GA4 Consent Mode v2: developers.google.com/tag-platform/security/guides/consent
- Meta Conversions API: developers.facebook.com/docs/marketing-api/conversions-api
- Google Ads Enhanced Conversions: support.google.com/google-ads/answer/9888656
- LookML CASE expression: cloud.google.com/looker/docs/reference/param-field-sql
- BigQuery partitioned tables: cloud.google.com/bigquery/docs/partitioned-tables