Attribution Issues | Blue Frog Docs

Attribution Issues

Diagnose and fix marketing attribution and UTM parameter problems that prevent accurate source tracking

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

  1. Click through your marketing links:

    • Email campaigns
    • Social media posts
    • Paid advertisements
    • Affiliate links
  2. Check URL in browser:

    Example: https://example.com/?utm_source=facebook&utm_medium=cpc&utm_campaign=summer_sale
    
  3. 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

  1. Check traffic source reports:

    • GA4 → Reports → Acquisition → Traffic acquisition
    • Look for "direct" traffic percentage
    • Review source/medium breakdown
  2. Compare to campaign URLs:

    • Check if campaigns appear in reports
    • Verify source/medium attribution
    • Look for "not set" values
  3. 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

  1. Create test link with UTM parameters:

    https://yoursite.com/?utm_source=test&utm_medium=email&utm_campaign=test_campaign
    
  2. Open link in incognito:

  3. 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

  1. Open Chrome DevTools:

    • Press F12
    • Navigate to Console tab
    • Type dataLayer and press Enter
  2. Check for campaign data:

    // Should see campaign parameters
    {
      campaign_source: 'facebook',
      campaign_medium: 'cpc',
      campaign_name: 'summer_sale'
    }
    
  3. 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:

  1. 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)
    
  2. Optional parameters:

    utm_term: Paid search keywords
    utm_content: A/B test variants or specific link
    
  3. Correct format example:

    https://example.com/product?utm_source=facebook&utm_medium=cpc&utm_campaign=summer_sale&utm_content=blue_ad
    
  4. Best 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 names
    
  5. URL 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:

  1. 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();
    }
    
  2. 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')
      };
    }
    
  3. 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:

  1. 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');
        }
      }
    }
    
  2. 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'));
    }
    
  3. 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:

  1. 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 : ''}`);
    });
    
  2. 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;
    
  3. htaccess redirects:

    # Preserve query string
    RewriteEngine On
    RewriteRule ^old-page$ /new-page [R=301,QSA,L]
    # QSA = Query String Append
    
  4. Nginx 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:

  1. Create custom dimensions:

    • GA4 → Configure → Custom definitions
    • Click "Create custom dimension"
    • Add parameters like gclid, fbclid
  2. 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')
    });
    
  3. 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:

  1. 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'));
      }
    }
    
  2. 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')
    });
    
  3. Auto-tagging in Google Ads:

    • 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:

  1. 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>
    
  2. 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:

Platform Troubleshooting Guide
Shopify Shopify Attribution Issues Guide
WordPress WordPress Attribution Issues Guide
Wix Wix Attribution Issues Guide
Squarespace Squarespace Attribution Issues Guide
Webflow Webflow Attribution Issues Guide

Verification

After implementing fixes:

  1. Test UTM parameter flow:

    • Create test link with UTM parameters
    • Click through to site
    • Navigate multiple pages
    • Verify parameters preserved
    • Check GA4 attribution
  2. Test click ID preservation:

    • Use link with gclid or fbclid
    • Navigate through site
    • Complete conversion
    • Verify click ID in conversion data
  3. Check GA4 reports:

    • Wait 24-48 hours
    • Review Traffic acquisition
    • Verify campaigns appearing correctly
    • Check "direct" traffic decreased
  4. Test redirect preservation:

    • Use 301/302 redirects
    • Verify query string preserved
    • Check attribution maintained

Common Mistakes

  1. Missing UTM parameters - Campaigns not tagged
  2. Inconsistent parameter naming - utm_Source vs utm_source
  3. Parameters lost on redirects - Query string not preserved
  4. UTM overwritten on return visits - First touch lost
  5. Not storing parameters in session - Lost on navigation
  6. Auto-tagging disabled in Google Ads - No gclid
  7. Special characters not encoded - Parameters malformed
  8. Different UTM on each page - Inconsistent attribution
  9. Not tracking both first and last touch - Incomplete data
  10. 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

Additional Resources

// SYS.FOOTER