Emtiaz
ServicesCase StudiesTestimonialsAboutFAQBlogs
Markting Analytics & Attribution9 min read

First-Touch vs Last-Touch Attribution: Building the Data Layer Schema

Emtiaz Hossain

Author Name

Emtiaz Hossain

Last Edit : Jul 20, 2026, 04:35:00 PM
First-Touch vs Last-Touch Attribution: Building the Data Layer Schema

TABLE OF CONTENTS▼
First-Touch vs Last-Touch Attribution: Building the Data Layer Schema in GTMWhat Is First-Touch vs Last-Touch Attribution?The Attribution JSON SchemaStep 1: cHTML Tag in Web GTMStep 2: JS Variable for Attribution DataStep 3: Pass as GA4 Event ParameterStep 4: Import sGTM Custom TemplateStep 5: Event Data Variables in sGTMStep 6: Create FT/LT VariablesStep 7: Use in TagsCommon IssuesLimitationsConclusion

Contents

0%
First-Touch vs Last-Touch Attribution: Building the Data Layer Schema in GTMWhat Is First-Touch vs Last-Touch Attribution?The Attribution JSON SchemaStep 1: cHTML Tag in Web GTMStep 2: JS Variable for Attribution DataStep 3: Pass as GA4 Event ParameterStep 4: Import sGTM Custom TemplateStep 5: Event Data Variables in sGTMStep 6: Create FT/LT VariablesStep 7: Use in TagsCommon IssuesLimitationsConclusion

First-Touch vs Last-Touch Attribution: Building the Data Layer Schema in GTM

First-touch attribution credits the first channel that brought a user to your site. Last-touch attribution credits the final interaction before conversion. With Google Tag Manager server-side tracking, capture both using localStorage JSON and a custom sGTM custom template.

This GTM attribution schema persists UTM parameters and Google Click IDs (gclid, gbraid, wbraid) across sessions. The first touch vs last touch data flows through GA4 event parameters into server-side GTM variables for Google Ads offline conversions, Google Sheets lead pipelines, and CRM webhooks.

What Is First-Touch vs Last-Touch Attribution?

First-touch assigns 100% credit to the first marketing channel. Last-touch assigns 100% credit to the last channel before conversion. For a useful marketing attribution model, you need both perspectives.

Example: User clicks Google Ad (gclid captured as first touch), leaves, returns 3 days later via organic search (utm_source=google as last touch), then converts. First-touch credits Google Ads. Last-touch credits organic search.

The Attribution JSON Schema

Stored in localStorage under attribution_data. The UTM parameter tracking schema sets first_touch once and updates last_touch on every page visit:

json
{
  "schema_version": 1,
  "first_touch": {
    "gclid": "Cj0K...",
    "gbraid": "",
    "wbraid": "",
    "utm_source": "google",
    "utm_medium": "cpc",
    "utm_campaign": "kitchen_remodel"
  },
  "last_touch": {
    "gclid": "",
    "gbraid": "",
    "wbraid": "",
    "utm_source": "google",
    "utm_medium": "organic",
    "utm_campaign": ""
  },
  "first_touch_timestamp": "2026-07-15 09:23:14",
  "last_touch_timestamp": "2026-07-17 14:05:32"
}

Step 1: cHTML Tag in Web GTM

Tag type: Custom HTML. Name: cHTML - URL Param store in LS. Trigger: Initialization - All Pages. Fires before GA4 loads.

javascript
<script>
(function () {
  var STORAGE_KEY = "attribution_data";
  var storage = {
    schema_version: 1,
    first_touch: {},
    last_touch: {},
    first_touch_timestamp: null,
    last_touch_timestamp: null
  };

  if (!window.location.search || window.location.search.length <= 1) {
    return;
  }

  try {
    var existing = JSON.parse(localStorage.getItem(STORAGE_KEY));
    if (existing) {
      storage = existing;
      if (!storage.schema_version) {
        storage.schema_version = 1;
      }
    }
  } catch (e) {}

  var params = {};
  var query = window.location.search.substring(1);
  var pairs = query.split("&");

  for (var i = 0; i < pairs.length; i++) {
    if (!pairs[i]) continue;
    var pair = pairs[i].split("=");
    var key = decodeURIComponent(pair[0]);
    var value = pair.length > 1
      ? decodeURIComponent(pair.slice(1).join("=").replace(/\+/g, " "))
      : "";
    if (key) params[key] = value;
  }

  if (Object.keys(params).length === 0) return;

  function formatTimestamp(d) {
    return d.getFullYear() + "-" +
      String(d.getMonth() + 1).padStart(2, "0") + "-" +
      String(d.getDate()).padStart(2, "0") + " " +
      String(d.getHours()).padStart(2, "0") + ":" +
      String(d.getMinutes()).padStart(2, "0") + ":" +
      String(d.getSeconds()).padStart(2, "0");
  }

  if (!storage.first_touch_timestamp) {
    storage.first_touch = params;
    storage.first_touch_timestamp = formatTimestamp(new Date());
  }

  storage.last_touch = params;
  storage.last_touch_timestamp = formatTimestamp(new Date());

  localStorage.setItem(STORAGE_KEY, JSON.stringify(storage));
})();
</script>

Step 2: JS Variable for Attribution Data

Create Custom JavaScript variable JS - Attribution Data:

javascript
function() {
  try {
    return JSON.stringify(
      JSON.parse(localStorage.getItem("attribution_data") || "{}")
    );
  } catch(e) {
    return "{}";
  }
}

Reads localStorage and serializes for passing as GA4 custom event parameter.

Step 3: Pass as GA4 Event Parameter

In each GA4 event tag (generate_lead, phone_click, email_click, map_click):

  • ▸Name: custom_properties
  • ▸Value: {{JS - Attribution Data}}

The GA4 event parameters carry the full attribution JSON to server-side GTM. The sGTM GA4 client receives it and makes it available to server-side GTM variables.

Step 4: Import sGTM Custom Template

Download the Attribution JSON Parser from GitHub:

  • ▸https://github.com/EmtiazHossainE2/attribution-json-parser
  • ▸Code > Download ZIP > unzip > Attribution JSON Parser.tpl
  • ▸sGTM: Templates > New > Import > select .tpl > Save

Inputs: jsonInput (serialized JSON) + path (dot-notation like first_touch.gclid)

Step 5: Event Data Variables in sGTM

Create Event Data Variable, keyPath custom_properties, name ED - custom_properties. Feeds the GA4 event parameter into the parser.

Step 6: Create FT/LT Variables

For each field: variable > type Attribution JSON Parser. jsonInput = {{ED - custom_properties}}. Path = field path.

First-touch:

  • ▸FT - gclid
  • ▸FT - gbraid
  • ▸FT - wbraid
  • ▸FT - utm_source
  • ▸FT - utm_medium
  • ▸FT - utm_campaign
  • ▸FT - utm_term
  • ▸FT - utm_content
  • ▸FT - timestamp

Last-touch:

  • ▸LT - gclid
  • ▸LT - gbraid
  • ▸LT - wbraid
  • ▸LT - utm_source
  • ▸LT - utm_medium
  • ▸LT - utm_campaign
  • ▸LT - utm_term
  • ▸LT - utm_content
  • ▸LT - timestamp

Also add schema_version.

Step 7: Use in Tags

  • ▸Google Sheets leads pipeline: map FT/LT attribution to columns for lead tracking per row
  • ▸Google Ads offline conversion: pass gclid and gbraid for offline conversion import and click matching
  • ▸CRM webhooks: include FT/LT data in payloads to Workiz, HubSpot, Salesforce

Common Issues

  • ▸custom_properties not showing: check GA4 tag param key matches exactly
  • ▸Variables return empty: verify ED - custom_properties exists and cHTML fires before GA4 tag
  • ▸First-touch resets: check timestamp logic only sets when null
  • ▸Template import fails: ensure .tpl file. Import from Templates, not Tags.

Limitations

  • ▸Client-side only: localStorage clears or cross-device breaks chain
  • ▸First-touch needs landing page URL params
  • ▸No cross-domain tracking: each domain has its own localStorage

Conclusion

Building FT/LT attribution in GTM gives you both top-of-funnel discovery and bottom-of-funnel conversion visibility. Lightweight approach works with any GA4 property or ad platform supporting URL parameters.

Get the template: https://github.com/EmtiazHossainE2/attribution-json-parser

#GTM#Marketing Attribution#UTM Attribution#JavaScript#sGTM#google analytics 4

Discussed this on LinkedIn

Found this helpful?

Drop a comment on the LinkedIn post – I read every reply and love connecting with tracking folks.

View on LinkedIn

Not Sure If Your Tracking Is Costing You?

Book a free 30-minute call. I will review your current tracking setup, show you where conversions are leaking, and tell you exactly what I would fix.

More Blog Posts

View all
The GA4 & GTM Audit That Found a Shopify Store's Google Ads Data Was 2x Inflated
Audit and Reporting

The GA4 & GTM Audit That Found a Shopify Store's Google Ads Data Was 2x Inflated

A full GA4/GTM/Google Ads audit on a UK Shopify store spending £18,000+/month found duplicate conversion tags inflating reported purchases 2x, a dropped GCLID at checkout, and misconfigured variables silently degrading Enhanced Conversions. Here's the audit, the fixes, and the migration off a legacy GTM setup before Shopify's deprecation deadline.

Complete LinkedIn Conversion Tracking Setup
Advanced Tracking

Complete LinkedIn Conversion Tracking Setup

How a UK marketing agency's LinkedIn Insight Tag upgrade turned into a full conversion audit, catching duplicate tags and stale triggers across eight lead-gen conversions.

Fixing Double Form Submission Tracking in GTM
Audit and Reporting

Fixing Double Form Submission Tracking in GTM

Duplicate form conversions in GA4 are usually a reload firing the same tag twice, not a real second submission. Here's the Custom JavaScript variable that catches it, plus other common causes to rule out.

Emtiaz
EmailLinkedInPrivacyTerms

© 2026 Emtiaz Hossain