Attribution Issues
What This Means
Attribution issues occur when analytics platforms fail to correctly identify the source of traffic, conversions, or user actions. This commonly involves UTM parameters being lost, overwritten, or misconfigured, leading to inaccurate marketing performance data.
Impact on Your Business
Marketing ROI Unknown:
- Cannot identify which campaigns drive conversions
- Budget allocation based on guesswork
- Successful campaigns appear unsuccessful
- Poor performers get continued investment
Attribution Data Loss:
- Traffic shows as "direct" when it's not
- Paid campaigns credited as organic
- Email campaigns not tracked
- Social media ROI invisible
Budget Waste:
- Over-investing in underperforming channels
- Cutting budget from successful campaigns
- Cannot optimize ad spend effectively
- Missed opportunities for scaling
How to Diagnose
Method 1: Check URL Parameters
Click through your marketing links:
- Email campaigns
- Social media posts
- Paid advertisements
- Affiliate links
-
Example: https://example.com/?utm_source=facebook&utm_medium=cpc&utm_campaign=summer_sale Navigate to another page:
- Click internal link
- Check if parameters persist
- Look for parameter loss
What to Look For:
- UTM parameters missing from initial landing
- Parameters lost on navigation
- Parameters overwritten by other values
- Malformed parameter syntax
Method 2: Review GA4 Acquisition Reports
Check traffic source reports:
- GA4 → Reports → Acquisition → Traffic acquisition
- Look for "direct" traffic percentage
- Review source/medium breakdown
Compare to campaign URLs:
- Check if campaigns appear in reports
- Verify source/medium attribution
- Look for "not set" values
Review campaign reports:
- GA4 → Advertising → Campaigns
- Check campaign names
- Verify UTM parameters captured
What to Look For:
- High percentage of "direct / (none)"
- Known campaigns missing from reports
- "(not set)" in source/medium
- Organic traffic inflated
Method 3: Test UTM Parameter Preservation
Create test link with UTM parameters:
https://yoursite.com/?utm_source=test&utm_medium=email&utm_campaign=test_campaignOpen link in incognito:
- Navigate to landing page
- Click to another page
- Complete conversion action
Check GA4 Realtime:
- Reports → Realtime → Traffic sources
- Look for your test source
- Verify attribution maintained
What to Look For:
- Parameters captured on landing
- Attribution maintained through funnel
- Conversion credited to correct source
- No "direct" misattribution
Method 4: Inspect Data Layer
Open Chrome DevTools:
- Press F12
- Navigate to Console tab
- Type
dataLayerand press Enter
Check for campaign data:
// Should see campaign parameters { campaign_source: 'facebook', campaign_medium: 'cpc', campaign_name: 'summer_sale' }Navigate and check again:
- Click to another page
- Re-check dataLayer
- Verify data persists
What to Look For:
- Campaign data in dataLayer
- Parameters stored correctly
- Data persists across pages
- No overwriting of values
General Fixes
Fix 1: Properly Format UTM Parameters
Follow UTM naming conventions:
Required UTM parameters:
utm_source: Identifies traffic source (google, facebook, newsletter) utm_medium: Identifies medium (cpc, email, social) utm_campaign: Identifies campaign (summer_sale, product_launch)Optional parameters:
utm_term: Paid search keywords utm_content: A/B test variants or specific linkCorrect format example:
https://example.com/product?utm_source=facebook&utm_medium=cpc&utm_campaign=summer_sale&utm_content=blue_adBest practices:
✓ Use lowercase consistently ✓ Use underscores instead of spaces ✓ Be specific but concise ✓ Use consistent naming scheme ✗ Don't use spaces ✗ Don't use special characters ✗ Don't change naming mid-campaign ✗ Don't use vague namesURL encoding:
// Encode parameters with special characters const campaign = 'Summer Sale 2024!'; const encoded = encodeURIComponent(campaign); // Result: Summer%20Sale%202024%21
Fix 2: Preserve UTM Parameters Through Navigation
Maintain attribution across site navigation:
Store UTM parameters on landing:
// Store UTM parameters in session storage function captureUTMParameters() { const params = new URLSearchParams(window.location.search); const utmParams = {}; ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'].forEach(param => { if (params.has(param)) { utmParams[param] = params.get(param); sessionStorage.setItem(param, params.get(param)); } }); return utmParams; } // Run on page load if (window.location.search.includes('utm_')) { captureUTMParameters(); }Retrieve stored parameters:
function getStoredUTMParameters() { return { source: sessionStorage.getItem('utm_source'), medium: sessionStorage.getItem('utm_medium'), campaign: sessionStorage.getItem('utm_campaign'), term: sessionStorage.getItem('utm_term'), content: sessionStorage.getItem('utm_content') }; }Send with conversions:
// Include UTM parameters in conversion events gtag('event', 'purchase', { transaction_id: 'T123', value: 99.99, currency: 'USD', // Add stored campaign data campaign_source: sessionStorage.getItem('utm_source'), campaign_medium: sessionStorage.getItem('utm_medium'), campaign_name: sessionStorage.getItem('utm_campaign') });
Fix 3: Fix First-Touch Attribution Overwriting
Preserve original attribution:
Only capture UTM on first visit:
function captureFirstTouchAttribution() { // Check if attribution already stored if (!sessionStorage.getItem('first_touch_captured')) { const params = new URLSearchParams(window.location.search); if (params.has('utm_source')) { // Store first-touch attribution sessionStorage.setItem('first_touch_source', params.get('utm_source')); sessionStorage.setItem('first_touch_medium', params.get('utm_medium')); sessionStorage.setItem('first_touch_campaign', params.get('utm_campaign')); sessionStorage.setItem('first_touch_captured', 'true'); } } }Don't overwrite on subsequent visits:
// Good - preserves first touch if (!sessionStorage.getItem('utm_source') && params.has('utm_source')) { sessionStorage.setItem('utm_source', params.get('utm_source')); } // Bad - overwrites every time if (params.has('utm_source')) { sessionStorage.setItem('utm_source', params.get('utm_source')); }Track both first and last touch:
// Store both attribution models function trackAttribution() { const params = new URLSearchParams(window.location.search); if (params.has('utm_source')) { // Always update last touch sessionStorage.setItem('last_touch_source', params.get('utm_source')); // Only set first touch if not already set if (!sessionStorage.getItem('first_touch_source')) { sessionStorage.setItem('first_touch_source', params.get('utm_source')); } } }
Fix 4: Fix Redirect Parameter Loss
Preserve UTM through redirects:
Server-side redirects (preserve query string):
// Node.js/Express example app.get('/old-page', (req, res) => { const query = new URLSearchParams(req.query).toString(); res.redirect(301, `/new-page${query ? '?' + query : ''}`); });Client-side redirects:
// Preserve all parameters const currentURL = new URL(window.location.href); const redirectURL = new URL('https://example.com/new-page'); // Copy all query parameters currentURL.searchParams.forEach((value, key) => { redirectURL.searchParams.set(key, value); }); window.location.href = redirectURL.href;htaccess redirects:
# Preserve query string RewriteEngine On RewriteRule ^old-page$ /new-page [R=301,QSA,L] # QSA = Query String AppendNginx redirects:
# Preserve query parameters location /old-page { return 301 /new-page$is_args$args; }
Fix 5: Configure GA4 to Recognize Custom Parameters
Track additional marketing parameters:
Create custom dimensions:
Register event parameters:
gtag('event', 'page_view', { // Standard parameters page_path: window.location.pathname, // Custom marketing parameters gclid: new URLSearchParams(window.location.search).get('gclid'), fbclid: new URLSearchParams(window.location.search).get('fbclid'), msclkid: new URLSearchParams(window.location.search).get('msclkid') });Create custom channel grouping:
- GA4 → Admin → Data display → Channel groups
- Create rules for custom channels
- Map parameters to channel groups
Fix 6: Fix Facebook/Google Click ID Tracking
Preserve ad platform click IDs:
Capture click IDs on landing:
function captureClickIDs() { const params = new URLSearchParams(window.location.search); // Google Ads click ID if (params.has('gclid')) { sessionStorage.setItem('gclid', params.get('gclid')); // Send to GA4 gtag('set', 'campaign', { 'gclid': params.get('gclid') }); } // Facebook click ID if (params.has('fbclid')) { sessionStorage.setItem('fbclid', params.get('fbclid')); } // Microsoft click ID if (params.has('msclkid')) { sessionStorage.setItem('msclkid', params.get('msclkid')); } }Include in conversion events:
gtag('event', 'purchase', { transaction_id: 'T123', value: 99.99, // Include click IDs for attribution gclid: sessionStorage.getItem('gclid'), fbclid: sessionStorage.getItem('fbclid') });-
- Google Ads → Settings → Account settings
- Enable "Auto-tagging"
- Automatically adds gclid parameter
- Verify not being stripped by redirects
Fix 7: Fix Form Submission Attribution
Maintain attribution through form flows:
Include UTM in hidden form fields:
<form id="contact-form"> <input type="text" name="name" required> <input type="email" name="email" required> <!-- Hidden fields for attribution --> <input type="hidden" name="utm_source" id="utm_source"> <input type="hidden" name="utm_medium" id="utm_medium"> <input type="hidden" name="utm_campaign" id="utm_campaign"> <button type="submit">Submit</button> </form> <script> // Populate hidden fields from session storage document.getElementById('utm_source').value = sessionStorage.getItem('utm_source') || ''; document.getElementById('utm_medium').value = sessionStorage.getItem('utm_medium') || ''; document.getElementById('utm_campaign').value = sessionStorage.getItem('utm_campaign') || ''; </script>Track form submission with attribution:
form.addEventListener('submit', function(e) { e.preventDefault(); gtag('event', 'generate_lead', { campaign_source: sessionStorage.getItem('utm_source'), campaign_medium: sessionStorage.getItem('utm_medium'), campaign_name: sessionStorage.getItem('utm_campaign'), event_callback: function() { form.submit(); } }); });
Platform-Specific Guides
Detailed implementation instructions for your specific platform:
Verification
After implementing fixes:
Test UTM parameter flow:
- Create test link with UTM parameters
- Click through to site
- Navigate multiple pages
- Verify parameters preserved
- Check GA4 attribution
Test click ID preservation:
- Use link with gclid or fbclid
- Navigate through site
- Complete conversion
- Verify click ID in conversion data
Check GA4 reports:
- Wait 24-48 hours
- Review Traffic acquisition
- Verify campaigns appearing correctly
- Check "direct" traffic decreased
Test redirect preservation:
- Use 301/302 redirects
- Verify query string preserved
- Check attribution maintained
Common Mistakes
- Missing UTM parameters - Campaigns not tagged
- Inconsistent parameter naming - utm_Source vs utm_source
- Parameters lost on redirects - Query string not preserved
- UTM overwritten on return visits - First touch lost
- Not storing parameters in session - Lost on navigation
- Auto-tagging disabled in Google Ads - No gclid
- Special characters not encoded - Parameters malformed
- Different UTM on each page - Inconsistent attribution
- Not tracking both first and last touch - Incomplete data
- Form submissions without attribution - Lead source unknown
Troubleshooting Checklist
- UTM parameters properly formatted
- Parameters captured on landing page
- Attribution stored in sessionStorage
- Parameters preserved through navigation
- Redirects maintain query strings
- Click IDs (gclid, fbclid) captured
- GA4 custom dimensions configured
- First-touch attribution not overwritten
- Forms include hidden UTM fields
- Auto-tagging enabled in ad platforms
- Tested in incognito mode
- GA4 shows correct attribution