📌 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 usually reports fewer orders than SFCC; in the projects I’ve audited the gap ranged from about 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, a session with no identifiable source shows as “(direct) / (none)”; when ITP has deleted the cookie, GA4 can’t link it to the earlier paid visit. 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, so GA4-based channel reports undervalue paid search. Google Ads may still count the conversion through its own tag and enhanced conversions — compare both before you cut budgets.
This is the structural reality for any business selling products with a consideration cycle longer than 7 days to Safari users (ITP deletes script-written cookies after 7 days without interaction) — check their share in your own GA4 (Tech › Browser); on mobile it is often a third or more.
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 feed GA4-based rates into bid calculations, check them against SFCC first — they are biased downward.
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 ${order_origin} = 'tracked_web'
THEN '1. High'
WHEN ${order_origin} IN ('offline_store','offline_callcenter','b2b_wholesale','subscription_automated')
THEN '2. Structural gap'
WHEN ${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 ${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 ${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 ${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: "Internal target: below 15% (set from our baseline). Investigate any sudden rise."
}
measure: sfcc_net_revenue {
type: sum
sql: ${TABLE}.sfcc_net_revenue ;;
value_format_name: decimal_2
}
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
Related reading
- GA4 Offline Conversions: A Step-by-Step Tutorial (CSV Import and Measurement Protocol)
- Why GA4, SFCC and Offline Orders Will Never Match — And What That Gap Actually Tells You
- The Transaction ID Problem: A Complete Diagnostic Guide to Fixing Your GA4/SFCC Join
Sources and Further Reading
- Apple ITP — official Webkit documentation: webkit.org/tracking-prevention
- Safari ITP and web analytics — Simo Ahava: simoahava.com
- Measure web pages in WebViews (GA4): developers.google.com/analytics/devguides/collection/ga4/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