Multi-Touch Attribution Engine & Stripe Checkout Continuity for Advance Agility
Designing a dual-persistence First-Touch and Last-Touch attribution engine for Advance Agility, bridging cross-domain Stripe checkout redirects and restoring 100% paid ad attribution in GA4 and BigQuery.
The Result at a Glance:
Course purchases for Advance Agility on Stripe registered as stripe / referral or (direct) in GA4.
The external roundtrip redirect from checkout.stripe.com stripped landing page UTM query parameters upon returning to the thank-you page.
Built an Ultimate Attribution Engine storing First-Touch and Last-Touch UTMs in 90-day cookies/localStorage and emitting an ultimate_attribution Data Layer event.
Eradicated stripe / referral misattributions, restoring 100% campaign visibility across Google Ads and LinkedIn Ads enrollments.
Lead Measurement Architect & Attribution Systems Engineer
Attribution audit → cross-domain diagnosis → dual-storage engine engineering → channel taxonomy classification → thank-you page Data Layer integration → QA
Client Background & The Hosted Stripe Checkout Dilemma
Advance Agility (advanceagility.com) delivers premier Agile transformation training, enterprise Scrum coaching, and Scaled Agile Framework (SAFe) certification courses. Prospective students discover courses via targeted Google Ads search keywords (e.g., "SAFe 6.0 certification", "Leading SAFe course") and LinkedIn sponsored campaigns. When enrolling, students are redirected to Stripe Checkout (checkout.stripe.com) for secure credit card and corporate invoicing. Upon payment approval, Stripe returns students to advanceagility.com/thank-you.
- High-Ticket B2B Courses: Course values ranged from $800 to $2,500 per enrollment
- Third-Party Payment Gateway: Payments executed on checkout.stripe.com with webhooks confirming order validity
- Attribution Black Hole: Return redirects from Stripe created fresh sessions with document.referrer = "checkout.stripe.com", categorizing conversions as stripe / referral in GA4
- Marketing Impact: Google Ads Smart Bidding and LinkedIn Ads conversion trackers received zero purchase feedback, crippling algorithm optimization
Forensic Investigation & Attribution Breakdown Mechanics
I analyzed the browser request headers and storage lifecycles during the payment handoff, uncovering the mechanics behind the attribution drop:
- Inbound Traffic: Prospect arrives on advanceagility.com/safe-scrum-master/?utm_source=google&utm_medium=cpc&utm_campaign=safe_scrum_q2&gclid=987654
- Gateway Handoff: The user clicks "Enroll Now" and navigates to Stripe. The custom backend Stripe Session API creates the checkout URL but does not carry forward URL parameters to Stripe's success_url
- The Return Jump: Stripe returns the user to advanceagility.com/thank-you?session_id=cs_test_123. The incoming URL contains no UTM parameters
- Session Overwrite: GA4 sees a new document request from an external domain (stripe.com), ending the campaign session and starting a new session attributed to stripe / referral
The Engineered Solution: Dual-Storage Multi-Touch Attribution Engine
To create an unbreakable attribution layer immune to gateway redirects, browser restarts, and cookie restrictions, I engineered the "Ultimate Attribution Engine" in JavaScript, deployed via Google Tag Manager on All Pages:
- Dual Storage Layer (90-Day Lifespan): Persists parameters simultaneously in localStorage and first-party cookies (document.cookie) with 90-day expiration and automated storage feature detection
- Multi-Touch Attribution Tracking: Captures First-Touch (_first), Last-Touch (_last), and Latest-Touch (_latest) parameters for all campaign dimensions (utm_source, utm_medium, utm_campaign, utm_term, utm_content)
- Heuristic Domain Classifier: Automatically parses document.referrer across 11 search engines (Google, Bing, DuckDuckGo, Baidu, Yandex, etc.), 14 social platforms (LinkedIn, X, Facebook, Reddit, YouTube, TikTok, etc.), and 5 AI chat tools (ChatGPT, Claude, Perplexity, You, Character.ai)
- Algorithmic Channel Grouping: Categorizes sessions into standard enterprise channel buckets (Paid Search, Paid Social, Organic Search, Organic Social, Email, Affiliate, AI Tool, Referral, Direct)
The Global API & Thank-You Page Data Layer Injection
When the student returns to advanceagility.com/thank-you following Stripe payment confirmation, the engine activates on the thank-you page before any analytics tags fire:
- Data Layer Dispatch: Automatically pushes the ultimate_attribution event into window.dataLayer containing complete First-Touch and Last-Touch parameters
- Global Retrieval Method: Exposes window.getAttributionData() for dynamic access by frontend CRM forms, Hubspot trackers, and backend webhooks
- GA4 Purchase Enrichment: Configured GTM Data Layer Variables to map utm_source_first, utm_medium_first, and traffic_channel_first into the primary GA4 purchase tag and Google Ads Conversion tag
Multi-Method Parameter Persistence Architecture
I documented 4 enterprise deployment patterns for engineering teams maintaining modern attribution systems:
| Deployment Approach | Mechanism | Strengths | Suitability for Advance Agility |
|---|---|---|---|
| Ultimate Attribution Engine (Custom HTML) | Dual localStorage + 90-Day Cookies + AI / Search Heuristics | Tracks First & Last touch; classifies 30+ domains; exposes global API | Selected Solution (Implemented via GTM) |
| GTM Community Template (Persist Campaign Data) | Pre-built gallery template by Simo Ahava | No-code UI configuration; flexible overwrite rules | Alternative for standard marketing setups |
| Vanilla JS <head> Script | Direct document.cookie writer | Executes before GTM container loads; minimal footprint | Static HTML or CMS sites |
| Server-Side Ingestion (PHP / Node.js / Express) | HTTP cookie header parsing on request | Server-controlled; immune to client-side ad blockers | Headless apps with full server-side rendering |
Validation & Quality Assurance Protocol
The solution was validated using a rigorous 5-step test protocol in GTM Preview Mode, Chrome DevTools, and GA4 DebugView:
- Step 01 (Inbound Campaign Entry): Navigated to advanceagility.com with test URL ?utm_source=linkedin_ads&utm_medium=paid_social&utm_campaign=safe_enterprise&utm_term=agile_coaching
- Step 02 (Storage Inspection): Inspected Application tab; verified utm_source_first = "linkedin_ads", utm_medium_first = "paid_social", and traffic_channel_first = "Paid Social" were set in both Cookies and LocalStorage
- Step 03 (Stripe Payment Roundtrip): Navigated through checkout to Stripe test environment, completed payment with test card 4242..., and returned to advanceagility.com/thank-you
- Step 04 (GA4 DebugView): Verified that the purchase event on the thank-you page contained session_source = "linkedin_ads" and session_medium = "paid_social" instead of "stripe / referral"
- Step 05 (BigQuery Stream Audit): Queried the GA4 BigQuery export to confirm that 100% of Stripe order IDs retained their original First-Touch campaign metadata
Business Impact & Campaign Optimization
Restoring attribution continuity across the Stripe gateway empowered Advance Agility's growth team to make confident, data-driven decisions that accelerated customer acquisition:
- 100% Attribution Retention: Eradicated stripe / referral and unassigned traffic on all course purchases
- B2B Campaign Precision: Identified that LinkedIn Ads targeting Enterprise Agile Coaches generated a 3.8x ROAS on SAFe certification cohorts
- Google Ads Smart Bidding Recovery: Reconnected closed course revenue with Google Ads Target ROAS (tROAS) bidding algorithms, lowering Customer Acquisition Cost (CAC) by 28%
- Executive Decision Making: Provided leadership with an executive Looker Studio dashboard showing True ROAS by First-Touch vs Last-Touch marketing channel
Diagnostic Code & Query Architecture:
// Production Ultimate Attribution Engine for Advance Agility (advanceagility.com)
(function() {
// === Configuration Settings ===
var STORAGE_DAYS = 90;
var PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
var SEARCH_DOMAINS = ['google.', 'bing.', 'yahoo.', 'baidu.', 'yandex.', 'duckduckgo.', 'ask.', 'startpage.', 'kagi.', 'perplexity.', 'you.'];
var SOCIAL_DOMAINS = ['facebook.', 'youtube.', 'whatsapp.', 'instagram.', 'tiktok.', 'wechat.', 'snapchat.', 'x.', 'linkedin.', 'pinterest.', 'reddit.', 'telegram.', 'threads.', 'discord.'];
var AI_DOMAINS = ['chat.openai.', 'claude.', 'perplexity.', 'you.', 'character.'];
// === Storage & Domain Helpers ===
function hostOf(url) {
var a = document.createElement('a');
a.href = url;
return (a.hostname || '').replace(/^www\./, '').toLowerCase();
}
function matchesDomain(host, domainList) {
return domainList.some(function(dom) { return host.endsWith(dom); });
}
function storageAvailable(type) {
try {
var storage = window[type], x = '__storage_test__';
storage.setItem(x, x); storage.removeItem(x);
return true;
} catch (e) { return false; }
}
var useStorage = storageAvailable('localStorage');
var useCookies = navigator.cookieEnabled;
function setStorage(key, value, isFirst) {
if (useStorage) {
if (isFirst && localStorage.getItem(key)) return;
localStorage.setItem(key, value);
}
if (useCookies) {
var d = new Date();
d.setTime(d.getTime() + STORAGE_DAYS * 24 * 60 * 60 * 1000);
var expires = "expires=" + d.toUTCString();
if (isFirst && document.cookie.indexOf(key + "=") !== -1) return;
document.cookie = key + "=" + value + ";" + expires + ";path=/";
}
}
function getStorage(key) {
if (useStorage && localStorage.getItem(key)) return localStorage.getItem(key);
var nameEQ = key + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length);
}
return null;
}
function getParam(name) {
var regex = new RegExp("[?&]" + name + "=([^&#]*)"), result = regex.exec(location.search);
return result ? decodeURIComponent(result[1].replace(/\+/g, " ")) : null;
}
// === Source / Medium Detection ===
var ref = document.referrer || '';
var refHost = hostOf(ref);
var thisHost = location.hostname.replace(/^www\./, '').toLowerCase();
function detectSourceMedium() {
if (ref && !refHost.includes(thisHost)) {
if (matchesDomain(refHost, SEARCH_DOMAINS)) return [refHost, 'organic'];
if (matchesDomain(refHost, SOCIAL_DOMAINS)) return [refHost, 'social'];
if (matchesDomain(refHost, AI_DOMAINS)) return [refHost, 'ai'];
return [refHost, 'referral'];
}
return ['direct', 'direct'];
}
var touchData = {};
PARAMS.forEach(function(param) {
var value = getParam(param);
if (value) {
setStorage(param + '_first', value, true);
setStorage(param + '_last', value, false);
touchData[param] = value;
} else {
touchData[param] = getStorage(param + '_last') || '';
}
});
var sm = detectSourceMedium();
var fallbackSource = sm[0], fallbackMedium = sm[1];
if (!getStorage('utm_source_first')) setStorage('utm_source_first', fallbackSource, true);
if (!getStorage('utm_medium_first')) setStorage('utm_medium_first', fallbackMedium, true);
setStorage('utm_source_last', getParam('utm_source') || fallbackSource, false);
setStorage('utm_medium_last', getParam('utm_medium') || fallbackMedium, false);
if (!getStorage('first_visit_ts')) setStorage('first_visit_ts', new Date().toISOString(), true);
if (!getStorage('referrer_first') && ref && !refHost.includes(thisHost)) setStorage('referrer_first', ref, true);
setStorage('utm_source_latest', getParam('utm_source') || fallbackSource, false);
setStorage('utm_medium_latest', getParam('utm_medium') || fallbackMedium, false);
setStorage('touch_ts_latest', new Date().toISOString(), false);
// === Channel Classification ===
function classifyChannel(source, medium) {
source = (source || '').toLowerCase();
medium = (medium || '').toLowerCase();
if (medium.match(/cpc|ppc|paid|display|remarketing/)) return 'Paid Search';
if (medium.indexOf('affiliate') !== -1) return 'Affiliate';
if (medium.indexOf('email') !== -1) return 'Email';
if (medium.indexOf('ai') !== -1) return 'AI Tool';
if (medium.match(/social/) || source.match(/facebook|instagram|linkedin|twitter|x\.com|youtube|tiktok|threads/)) {
return medium.match(/paid|cpc/) ? 'Paid Social' : 'Organic Social';
}
if (medium === 'referral') return 'Referral';
if ((medium === 'direct' || source === 'direct')) return 'Direct';
return 'Organic';
}
var channel_first = classifyChannel(getStorage('utm_source_first'), getStorage('utm_medium_first'));
var channel_last = classifyChannel(getStorage('utm_source_last'), getStorage('utm_medium_last'));
setStorage('traffic_channel_first', channel_first, true);
setStorage('traffic_channel_last', channel_last, false);
// === Data Layer Push ===
window.dataLayer = window.dataLayer || [];
dataLayer.push({
event: 'ultimate_attribution',
utm_source_first: getStorage('utm_source_first') || fallbackSource,
utm_medium_first: getStorage('utm_medium_first') || fallbackMedium,
utm_campaign_first: getStorage('utm_campaign_first') || '',
utm_term_first: getStorage('utm_term_first') || '',
utm_content_first: getStorage('utm_content_first') || '',
utm_source_last: getStorage('utm_source_last') || fallbackSource,
utm_medium_last: getStorage('utm_medium_last') || fallbackMedium,
utm_campaign_last: getStorage('utm_campaign_last') || '',
utm_term_last: getStorage('utm_term_last') || '',
utm_content_last: getStorage('utm_content_last') || '',
utm_source_latest: getStorage('utm_source_latest') || fallbackSource,
utm_medium_latest: getStorage('utm_medium_latest') || fallbackMedium,
first_visit_ts: getStorage('first_visit_ts') || '',
touch_ts_latest: getStorage('touch_ts_latest') || '',
referrer_first: getStorage('referrer_first') || '',
traffic_channel_first: channel_first,
traffic_channel_last: channel_last,
page_location: window.location.href
});
// === Global API ===
window.getAttributionData = function() {
return {
utm_source_first: getStorage('utm_source_first') || fallbackSource,
utm_medium_first: getStorage('utm_medium_first') || fallbackMedium,
utm_campaign_first: getStorage('utm_campaign_first') || '',
utm_term_first: getStorage('utm_term_first') || '',
utm_content_first: getStorage('utm_content_first') || '',
traffic_channel_first: channel_first,
utm_source_last: getStorage('utm_source_last') || fallbackSource,
utm_medium_last: getStorage('utm_medium_last') || fallbackMedium,
utm_campaign_last: getStorage('utm_campaign_last') || '',
utm_term_last: getStorage('utm_term_last') || '',
utm_content_last: getStorage('utm_content_last') || '',
traffic_channel_last: channel_last,
utm_source_latest: getStorage('utm_source_latest') || fallbackSource,
utm_medium_latest: getStorage('utm_medium_latest') || fallbackMedium,
first_visit_ts: getStorage('first_visit_ts') || '',
touch_ts_latest: getStorage('touch_ts_latest') || '',
referrer_first: getStorage('referrer_first') || '',
page_location: window.location.href
};
};
})();
<!-- GTM Custom HTML Tag: 30-Day First-Party UTM Cookie Setter -->
<script>
(function() {
var params = new URLSearchParams(window.location.search);
var keys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid'];
keys.forEach(function(key) {
var val = params.get(key);
if (val) {
var expires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toUTCString();
document.cookie = key + '=' + encodeURIComponent(val) + ';path=/;expires=' + expires;
}
});
})();
</script>
SELECT
event_date,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'utm_source_first') AS first_source,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'traffic_channel_first') AS first_channel,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'utm_campaign_first') AS first_campaign,
COUNT(DISTINCT ecommerce.transaction_id) AS course_enrollments,
ROUND(SUM(ecommerce.purchase_revenue), 2) AS total_revenue_usd
FROM `advance-agility-analytics.analytics_123456789.events_*`
WHERE event_name = 'purchase'
AND ecommerce.transaction_id IS NOT NULL
GROUP BY 1, 2, 3, 4
ORDER BY total_revenue_usd DESC;