Skip to main content
When SourceMedium receives an order, we often have attribution data from multiple First-Party Inputs—Shopify visits, order custom attributes, existing GA4 tracking, and post-purchase surveys. The Attribution Source Hierarchy is our Resolution Strategy. It determines how we intelligently resolve conflicts when data is available from multiple places, ensuring we always use the most granular and reliable signal available for every single order.
This is a last-click (UTM-based) attribution system. We prioritize data sources that capture the customer’s most recent measurable marketing touch before purchase.

Primary Attribution Source Hierarchy

The order attribution system evaluates data sources in this specific priority order:
PrioritySource TypeDescription
1Shopify Custom Attributes OverrideOrder-level attribution set via Shopify customAttributes (Checkout / Admin GraphQL API). Treated as an explicit override (shopify_custom_attribute_override).
2Shopify Last Customer VisitThe final tracked visit before order placement, captured by Shopify’s customer visit tracking
3Shopify Landing SiteUTM parameters extracted from the order’s landing page URL
4Shopify Order Notes (Legacy)UTM data written to order notes / note attributes by tracking tools (Elevar, Blotout, etc.)
5Website Event TrackingFirst-party event pixels from GA4, Elevar, or other website analytics
6Shopify First Customer VisitThe customer’s first tracked visit, captured by Shopify’s customer visit tracking
7GA4Transaction data from Google Analytics 4
8Shopify Order Referring Site UTMsUTM parameters parsed from the order’s referring_site URL
If data is missing at priority 1, the system “falls” to priority 2, and so on until it finds valid UTM data.
Universal Analytics (UA) has been sunset by Google. If your organization already exported and retained historical UA data, treat it as historical-only and prefer GA4 for ongoing tracking.
Why does “Last Customer Visit” beat “First Customer Visit”?Because we’re building a last-click attribution model. The most recent touchpoint before purchase (last visit) is more relevant for understanding what drove the conversion than the first touchpoint (which may have been weeks earlier).

Website Event Tracking Details

For website event tracking sources (priority 5), the system uses qualifying events within a 90-day lookback window before the order. When multiple events exist, they’re ranked by:
  1. Days between event and order (closer to order = higher priority)
  2. Event timestamp (earlier event_local_datetime wins ties)
  3. Event ID and source system (as final tie-breakers)
If a website event has a UTM source, that’s used directly. If only a referrer domain is available, the source is inferred from the domain and the medium defaults to referral.

Shopify Custom Attributes Override (Priority 1)

If your Shopify order has attribution data written to customAttributes (Shopify Admin GraphQL API / Checkout attributes), SourceMedium treats it as an explicit override and prioritizes it ahead of the default Shopify attribution sources (visits, landing site, website events, GA4). This is the recommended mechanism for:
  • Backfilling attribution onto historical orders
  • Capturing UTMs at checkout when cookie-based tracking is unreliable (ad blockers, ITP, cross-domain, etc.)

Supported keys (allowlist)

Only a specific set of keys are extracted from customAttributes to prevent accidental capture of non-attribution data. Keys are normalized (case + delimiter + snake/camel agnostic) before matching:
  • utm_source, utmSource, UTM_SOURCE, utm-source → treated as the same key
  • sm_utmParams, smUtmParams → treated as the same key
  • GE_utmParams, ge_utm_params → treated as the same key
KeyPurpose
sm_utm_source, sm_utm_medium, sm_utm_campaign, sm_utm_content, sm_utm_term, sm_utm_idSourceMedium override keys (recommended to avoid collisions with other apps)
utm_source, utm_medium, utm_campaign, utm_content, utm_term, utm_idStandard UTM keys
sm_utmParams, utmParams, GE_utmParamsAggregate UTM query-string fields (parsed into individual UTM keys)
sm_referrer, referrerReferring URL

Conflict resolution (deterministic)

If your customAttributes data is messy (duplicate keys, multiple sources provided), SourceMedium applies a deterministic waterfall to resolve each final field.

Field-level precedence

For each UTM field, the first non-empty value wins (highest → lowest):
  1. Direct SM override key (sm_utm_*)
  2. Direct standard UTM key (utm_*)
  3. Parsed from sm_utmParams (query string)
  4. Parsed from utmParams (query string)
  5. Parsed from GE_utmParams (query string)
  6. utm_source only: click ID inference (scclidirclickidmsclkidttclidfbclidgclid)
referrer is resolved similarly: sm_referrerreferrer.

Duplicate keys (same tier)

If the same normalized key appears multiple times at the same tier (for example both utm_source and UTM_SOURCE, or repeated utm_source entries), SourceMedium de-dupes deterministically using MAX() (lexicographically largest value after decoding/cleaning). To avoid surprises, only set each key once.
Click IDs are processed as a fallback (see below), but the raw click ID values are not stored as attribution fields.

Aggregate UTM fields

If you send sm_utmParams, utmParams, or GE_utmParams as a URL query string (e.g., utm_source=google&utm_medium=cpc), SourceMedium parses it and extracts the individual UTM keys. Parsing is snake/camel agnostic (both utm_source= and utmSource= are supported) and URL-decoding is applied.

URL Handling

URL encoding in sm_utmParams, utmParams, and GE_utmParams values is fully decoded:
  • Percent encoding: %XX patterns (e.g., %20 → space, %26&)
  • Form-style encoding: + characters are decoded as spaces (common in checkout apps)

Click ID to Channel Inference

When only a click ID is present (no explicit utm_source), the system infers a fallback utm_source value:
Priority (highest first)Click IDInferred utm_sourcePlatform
1scclidsnapchatSnapchat Ads
2irclickidimpactImpact (Affiliate)
3msclkidmicrosoftMicrosoft/Bing Ads
4ttclidtiktokTikTok Ads
5fbclidmetaMeta (Facebook/Instagram)
6gclidgoogleGoogle Ads
If an order has multiple click IDs, the highest-priority click ID wins. This preserves more specific intent signals (like affiliates or smaller platforms) over ambient IDs from high-volume platforms. Click IDs are checked in this order:
  1. Direct customAttributes click ID keys (e.g., scclid, gclid)
  2. Click IDs embedded inside utmParams
  3. Click IDs embedded inside GE_utmParams
Click IDs are fallback only. If an explicit utm_source exists, it takes precedence over any click ID inference.
Use Shopify Checkout UI Extensions to write attribution to checkout attributes (which become order customAttributes).
import { useEffect } from 'react';
import { reactExtension, useApplyAttributeChange, useAttributes } from '@shopify/ui-extensions-react/checkout';

export default reactExtension('purchase.checkout.block.render', () => <AttributionCapture />);

function AttributionCapture() {
  const applyAttributeChange = useApplyAttributeChange();
  const attributes = useAttributes();

  useEffect(() => {
    void setAttribution();
  }, []);

  function getAttributeValue(key: string): string | null {
    return attributes.find(attr => attr.key === key)?.value ?? null;
  }

  async function setAttribution() {
    const attribution = {
      // Prefer explicit SM overrides if present; otherwise map from standard UTM attributes.
      sm_utm_source: getAttributeValue('sm_utm_source') ?? getAttributeValue('utm_source'),
      sm_utm_medium: getAttributeValue('sm_utm_medium') ?? getAttributeValue('utm_medium'),
      sm_utm_campaign: getAttributeValue('sm_utm_campaign') ?? getAttributeValue('utm_campaign'),
      sm_utm_content: getAttributeValue('sm_utm_content') ?? getAttributeValue('utm_content'),
      sm_utm_term: getAttributeValue('sm_utm_term') ?? getAttributeValue('utm_term'),
      sm_utm_id: getAttributeValue('sm_utm_id') ?? getAttributeValue('utm_id'),
    };

    for (const [key, value] of Object.entries(attribution)) {
      if (!value) continue;

      const result = await applyAttributeChange({
        type: 'updateAttribute',
        key,
        value: String(value),
      });

      if (result.type === 'error') {
        console.error('Failed to set attribution:', result.message);
      }
    }
  }

  return null;
}
Accelerated checkout methods (Apple Pay, Google Pay, Shop Pay express) may bypass checkout extension execution. Use this as a supplement to GA4/server-side tracking, not a single point of failure.
Checkout UI extensions are sandboxed and can’t access the browser DOM (e.g., document.cookie, localStorage, or window.location). Capture UTMs on the storefront and write them into cart/checkout attributes before checkout starts.
Custom attributes may be used by other apps. To reduce collisions, prefer sm_utm_* / sm_utmParams keys for explicit overrides (matching is normalized, but canonical keys reduce ambiguity).

Shopify Order Notes (Priority 4)

Order notes are a key attribution source written by server-side tracking tools like Elevar and Blotout. These tools capture UTMs at checkout and write them to Shopify order notes / note attributes.
If you have control over implementation, prefer customAttributes override (priority 1) instead of writing attribution into notes. Notes are shared and can be overwritten by apps; custom attributes are clearer and explicitly prioritized.

Note Attributes (Legacy)

The system supports extracting attribution from legacy note_attributes patterns, including:
  • Standard UTM keys like utm_source, utm_medium, utm_campaign, utm_content, utm_term, utm_id
  • Tool-specific payloads (e.g., _elevar_visitor_info) when they contain nested UTM data

UTM Field Collapsing

After the primary source is determined, the system “collapses” supplementary UTM fields from lower-priority sources. Example: If priority 2 (Last Customer Visit) provides utm_source=google but no utm_campaign, and priority 3 (Landing Site) has utm_campaign=summer_sale, the final attribution will combine:
  • utm_source=google (from priority 2)
  • utm_campaign=summer_sale (from priority 3)
This only happens when the utm_source grouped channel is the same across sources—we don’t mix data from different channels.