technical-guidesUpdated 2025

URL Parsing Corruption: When Multiple ? Breaks Tracking

Multiple question marks corrupt URL parameter parsing. Learn how browsers handle malformed URLs and why GA4 misses campaign data.

7 min readtechnical-guides

Your URL has multiple question marks. Your browser's URL parser is confused. GA4 receives corrupted data.

This is the technical deep-dive into URL parsing corruption and why RFC 3986 allows only ONE question mark per URL.

🚨 Not sure what's breaking your tracking?

Run a free 60-second audit to check all 40+ ways UTM tracking can fail.

Scan Your Campaigns Free

✓ No credit card ✓ See results instantly

How URL Parsers Work

Modern browsers use standardized URL parsers based on WHATWG URL Standard:

Javascript
const url = new URL('https://shop.com?param1=value1&param2=value2');
 
console.log(url.origin);      // https://shop.com
console.log(url.pathname);    // /
console.log(url.search);      // ?param1=value1&param2=value2
console.log(url.searchParams.get('param1')); // value1

The parser expects:

  1. ONE question mark ? starting the query string
  2. Parameters separated by ampersands &
  3. Key-value pairs using equal signs =

What Happens with Multiple Question Marks

Example 1: Two Question Marks

Javascript
const url = new URL('https://shop.com?page=1?utm_source=facebook');
 
console.log(url.search);      // ?page=1?utm_source=facebook
console.log(url.searchParams.get('page'));       // "1?utm_source=facebook"
console.log(url.searchParams.get('utm_source')); // null

What happened:

  • Parser found first ? at position after domain
  • Everything after first ? = query string
  • Second ? treated as part of the value for page
  • utm_source never parsed as a separate parameter

Example 2: Three Question Marks

Javascript
const url = new URL('https://shop.com?a=1?b=2?c=3');
 
console.log(url.searchParams.get('a')); // "1?b=2?c=3"
console.log(url.searchParams.get('b')); // null
console.log(url.searchParams.get('c')); // null

Only the first parameter is parsed. Everything after becomes part of its value.

😰 Is this your only tracking issue?

This is just 1 of 40+ ways UTM tracking breaks. Most marketing teams have 8-12 critical issues they don't know about.

• 94% of sites have UTM errors

• Average: $8,400/month in wasted ad spend

• Fix time: 15 minutes with our report

✓ Connects directly to GA4 (read-only, secure)

✓ Scans 90 days of data in 2 minutes

✓ Prioritizes issues by revenue impact

✓ Shows exact sessions affected

Get Your Free Audit Report

Impact on GA4 Tracking

GA4's measurement protocol expects clean parameter parsing:

Javascript
// GA4 extracts UTMs like this
const params = new URLSearchParams(window.location.search);
const source = params.get('utm_source');
const medium = params.get('utm_medium');
const campaign = params.get('utm_campaign');

With Multiple Question Marks

Javascript
// URL: site.com?page=home?utm_source=facebook&utm_medium=cpc
 
const params = new URLSearchParams(window.location.search);
// search = "?page=home?utm_source=facebook&utm_medium=cpc"
 
params.get('page');         // "home?utm_source=facebook&utm_medium=cpc"
params.get('utm_source');   // null
params.get('utm_medium');   // null

Result: GA4 attributes traffic as "Direct" (no campaign parameters found).

Server-Side vs Client-Side Parsing

Server-Side (What Your Server Receives)

Python
# Flask example
from flask import request
 
@app.route('/page')
def page():
    # URL: site.com?a=1?b=2
    print(request.args.get('a'))  # "1?b=2"
    print(request.args.get('b'))  // None

Servers follow the same parsing rules: Only one ? marks the query string start.

Client-Side (JavaScript)

Javascript
// Same behavior in browser
const urlParams = new URLSearchParams(window.location.search);
urlParams.get('a'); // "1?b=2"
urlParams.get('b'); // null

Both client and server see corrupted data.

RFC 3986: The URL Standard

The Internet Engineering Task Force (IETF) defines URL structure in RFC 3986:

Code
URI = scheme:[//authority]path[?query][#fragment]

Key rules:
1. Query component BEGINS with "?" delimiter
2. Query component ENDS with "#" (fragment) or end of URI
3. Only ONE "?" character delimits start of query
4. Within query, "&" separates parameters

From RFC 3986 Section 3.4:

The query component is indicated by the first question mark ("?") character and terminated by a number sign ("#") character or by the end of the URI.

Implication: The FIRST ? starts the query. Any additional ? characters are part of the query string data, not delimiters.

How Browsers Handle Malformed URLs

Different browsers may handle edge cases slightly differently, but all follow WHATWG URL Standard:

Chrome/Edge (Chromium)

Javascript
const url = new URL('https://site.com?a=1?b=2');
console.log(url.href);  // https://site.com?a=1?b=2
console.log(url.search); // ?a=1?b=2
// Second ? preserved as-is in query string

Firefox

Javascript
const url = new URL('https://site.com?a=1?b=2');
console.log(url.href);  // https://site.com?a=1?b=2
console.log(url.search); // ?a=1?b=2
// Same behavior as Chromium

Safari

Javascript
const url = new URL('https://site.com?a=1?b=2');
console.log(url.href);  // https://site.com?a=1?b=2
console.log(url.search); // ?a=1?b=2
// Consistent with other browsers

All modern browsers follow the same standard.

Debugging URL Parsing Issues

Method 1: Browser DevTools

Javascript
// Open console on your page
const current = window.location.href;
console.log('Full URL:', current);
console.log('Query string:', window.location.search);
console.log('Parsed params:', Object.fromEntries(new URLSearchParams(window.location.search)));

Expected output (healthy):

Code
Full URL: https://site.com?utm_source=facebook&utm_medium=cpc
Query string: ?utm_source=facebook&utm_medium=cpc
Parsed params: {utm_source: "facebook", utm_medium: "cpc"}

Corrupted output (multiple ?):

Code
Full URL: https://site.com?page=1?utm_source=facebook
Query string: ?page=1?utm_source=facebook
Parsed params: {page: "1?utm_source=facebook"}

Method 2: URL Validator Tool

Javascript
function validateURL(urlString) {
    try {
        const url = new URL(urlString);
 
        // Count question marks in full URL
        const questionMarkCount = (urlString.match(/\?/g) || []).length;
 
        if (questionMarkCount > 1) {
            console.error(`ERROR: ${"{"}{"{"}questionMarkCount{"}"}{"}"}} question marks found (maximum: 1)`);
            console.error(`Query string: ${url.search}`);
            console.error(`This will corrupt parameter parsing`);
            return false;
        }
 
        console.log('✅ URL structure is valid');
        return true;
 
    } catch (error) {
        console.error('Invalid URL:', error.message);
        return false;
    }
}
 
// Test
validateURL('https://site.com?a=1?b=2');
// ERROR: 2 question marks found (maximum: 1)

Real-World Corruption Scenarios

Scenario 1: Dynamic URL Building

Javascript
// E-commerce filter system
function buildProductURL(filters, utmParams) {
    let url = 'https://shop.com/products';
 
    // Add filters
    if (filters.category) {
        url += '?category=' + filters.category;
    }
    if (filters.price) {
        url += '?price=' + filters.price; // ❌ BUG: Should be &
    }
 
    // Add UTMs
    if (utmParams) {
        url += '?' + utmParams; // ❌ BUG: Should check for existing ?
    }
 
    return url;
}
 
// Result: shop.com/products?category=shoes?price=50?utm_source=email
// Three question marks!

Scenario 2: Server-Side Redirect

Python
# Flask redirect with UTMs
@app.route('/old-page')
def redirect_with_utm():
    base_url = request.args.get('redirect_url', '/home')
    # base_url = "https://site.com?welcome=true"
 
    utm_params = "utm_source=redirect&utm_medium=internal"
 
    # ❌ BUG: Doesn't check if base_url has parameters
    redirect_url = f"`{"{"}{"{"}base_url{"}"}{"}"}}`?`{"{"}{"{"}utm_params{"}"}{"}"}}`"
    # Result: https://site.com?welcome=true?utm_source=redirect
 
    return redirect(redirect_url)

✅ Fixed this issue? Great! Now check the other 39...

You just fixed one tracking issue. But are your Google Ads doubling sessions? Is Facebook attribution broken? Are internal links overwriting campaigns?

Connects to GA4 (read-only, OAuth secured)

Scans 90 days of traffic in 2 minutes

Prioritizes by revenue impact

Free forever for monthly audits

Run Complete UTM Audit (Free Forever)

Join 2,847 marketers fixing their tracking daily

FAQ

Can I URL-encode the second question mark?

Yes: ?param1=value%3Fparam2=value2. But this is confusing. Better to use & as the separator.

Do all URL parsers behave the same way?

Yes. All modern browsers and server frameworks follow RFC 3986 and WHATWG URL Standard.

What if I need a question mark in a parameter value?

URL-encode it: ?message=hello%3Fworld (%3F is the encoded ?).

Will fixing this break historical data?

Historical data with multiple ? will remain corrupted. The fix only affects future traffic.

How do I find URLs with multiple question marks?

Search your codebase for patterns like url + '?' or regex \?.*\?.

Conclusion

Multiple question marks corrupt URL parameter parsing because:

  1. RFC 3986 allows only ONE ? to start query strings
  2. Browsers treat subsequent ? as part of parameter values
  3. GA4 can't extract UTM parameters from corrupted query strings

Fix: Use ? for the first parameter, & for all subsequent parameters.

Code
❌ CORRUPTED: site.com?a=1?b=2?c=3
✅ CORRECT:   site.com?a=1&b=2&c=3

Technical Reference: Multiple Question Marks Validation Rule

UTM

Get Your Free Audit in 60 Seconds

Connect GA4, run the scan, and see exactly where tracking is leaking budget. No credit card required.

Trusted by growth teams and agencies to keep attribution clean.