UTM Parameters Lost After Redirects? A Practical GA4 Check
Trace UTM values across redirects, compare eight controlled cases, and separate URL changes from GA4 collection and reporting problems.
Like it ? share it
A campaign link can reach the right page with an HTTP 200 response and still arrive with different tracking information. The useful question is where the information first changed: in the redirect response, in the URL sent to Analytics, or in the report you are reading.
Start with the exact link your audience receives. Compare the intended UTM values with the resolved URL at every redirect, then inspect the first relevant Analytics event. Treat a successful page load and successful campaign collection as separate checks.
This guide uses eight controlled redirect cases to make those boundaries visible. It is a campaign-link QA procedure, not a prediction about how often real websites lose attribution.
A redirect status does not promise to preserve your query
Suppose an email contains this link:
https://example.com/start?utm_source=newsletter&utm_medium=email&utm_campaign=autumn_launchThe server responds:
HTTP/1.1 302 Found
Location: /landingThe destination has a new path and no query. The original campaign query is not automatically appended to that destination. If the intended result is a tagged landing URL, the redirect must produce it explicitly, for example:
Location: /landing?utm_source=newsletter&utm_medium=email&utm_campaign=autumn_launchThe same practical inspection applies to a permanent redirect: the status code alone does not tell you which query values survived. HTTP defines Location as a URI reference, and the reference-resolution rules determine the resulting path, query and fragment. The exact reference matters; a new path, a query-only reference and a fragment-only reference need not behave alike. HTTP Location semantics, URI reference resolution.
Eight cases: every destination loaded, but only two matched the plan
A local test for this guide sent GET requests through eight deliberately constructed HTTP 302 responses, each ending in HTTP 200. A Python 3.12.6 client followed the redirect. The destination recorded the request query; the check compared decoded lists of values with the three values planned for that case.
| Redirect output | Result at the destination | Exact match to planned UTMs? |
|---|---|---|
| All three tags explicitly retained | Source, medium and campaign match | Yes |
| Destination has no query | All three absent | No |
| Only source and medium retained | Campaign absent | No |
Tags placed inside an encoded next destination |
No top-level UTM fields | No |
Tags placed after #offer |
No UTM query in the HTTP request | No |
Intended source email+partner, encoded as email%2Bpartner |
Decoded source remains email+partner |
Yes |
Intended source email+partner, written as email+partner |
Form-style parsing gives email partner |
No |
Two different utm_source values |
Both values arrive; source is ambiguous | No |
The result was 2 exact matches from 8 fixtures, despite all eight destinations returning 200. The cases were chosen to demonstrate failure modes, so that fraction is not a real-world failure rate. This was an HTTP-client test: it did not execute browser scripts, send GA4 events, test consent, or measure attribution.
There are two especially useful lessons.
First, presence is weaker than equality. A checker that finds a parameter named utm_source will miss a changed value or a duplicate source.
Second, byte-for-byte URL equality is too strict. spring%20sale and spring+sale can decode to the same value, while a literal plus sign needs %2B under form-style parsing. Compare decoded values and duplicates, while preserving meaningful case. URL parsing standard, URL search-parameter serialization.
Reproduce the value check
This standard-library snippet lets you inspect a captured destination URL without visiting it:
from urllib.parse import parse_qs, urlsplit
expected = {
"utm_source": ["newsletter"],
"utm_medium": ["email"],
"utm_campaign": ["autumn_launch"],
}
landed = "https://example.com/landing?utm_source=newsletter&utm_medium=email"
query = parse_qs(urlsplit(landed).query, keep_blank_values=True)
for key, planned in expected.items():
actual = query.get(key, [])
print(key, {"planned": planned, "actual": actual, "matches": actual == planned})For this example, source and medium match; campaign is missing. An empty value and an absent value remain distinguishable. This snippet checks URL values only, not browser behavior or Analytics receipt.
Find the first boundary that changes the information
1. Save the actual outbound link
Keep the link copied from the delivered email, advertisement, QR destination or short-link system. A clean URL from the builder may differ from the wrapped link a recipient clicks.
Record the intended source, medium and campaign, the test time, and the device context. Use synthetic campaign labels for testing; keep personal information out of the URL. Prefer a test property or controlled environment when you need to generate Analytics traffic.
If you are still choosing names, use the UTM naming guide and UTM Builder. This investigation begins after those intended values are known.
2. Inspect each redirect, including the transition to the landing page
In browser developer tools, preserve the Network log before opening the test link. Inspect each document response's status and Location, then resolve the next URL and compare its campaign values.
An HTTP redirect checker can help inspect the server chain. You can start with the Redirect Detector, then verify the actual recipient journey in a browser. A server-only result cannot establish what a script, app webview or consent flow does afterward.
Look for the first change, rather than blaming the final page:
- A canonical-host or HTTPS redirect that constructs a clean path without the query.
- A campaign value removed while other values survive.
- Tracking fields encoded inside another URL instead of present on the landing URL itself.
- Different handling for duplicate or empty parameter values.
Capture observations before changing rules. A 200 landing response is evidence of an HTTP response, not evidence of campaign capture.
3. Keep query parameters and fragments separate
These are different structures:
/landing?utm_source=newsletter&utm_medium=email&utm_campaign=autumn_launch#offer
/landing#offer?utm_source=newsletter&utm_medium=email&utm_campaign=autumn_launchIn the first, campaign fields are in the query. In the second, the parameter-looking text is in the fragment. A browser does not send that fragment as the HTTP request query; client-side code can still read it. For ordinary campaign links, put the tracking query before # and verify the implementation if you rely on fragment handling. URL fragment reference.
The URL can be correct while the GA4 diagnosis is wrong
After the transport check passes, inspect collection and reporting separately. Do not keep rewriting a working redirect to solve a reporting-scope problem.
Inspect what was collected
Check the relevant event in your debugging workflow and inspect its page_location and campaign information. A page_location override is worth checking because GA4 normally takes this field from document.location, but configuration can replace it. The final address bar alone cannot prove which value was sent. GA4 configuration reference.
Use a controlled debug session and follow the DebugView setup. Privacy controls or missing Analytics-cookie consent can affect whether debug events appear. An empty debug stream is not, by itself, a diagnosis of malformed UTM fields.
Timing matters. If a page removes the query before the measurement uses it, campaign information may be unavailable at that collection point. If the initial event already captured the intended values and the user later visits an untagged page, the later address does not establish that the original values were lost.
Look in a dimension that actually shows the information
Google documents a particularly misleading detail: campaign information is omitted from Landing page + query string and Page path + query string, but can be found in Page location. Missing UTMs in those report labels therefore do not prove that a redirect stripped them. Google's campaign URL documentation.
Also choose the intended scope. First user dimensions concern initial user acquisition. Session dimensions concern session acquisition; the Traffic acquisition report uses that scope. A returning user's first acquisition and today's campaign visit answer different questions. Traffic-source dimension scopes.
GA4 also does not automatically start a new session merely because campaign or source information changes during an existing session. New values can be associated with relevant events without replacing that session's original source. Do not use an internal tagged click as a clean test of a new campaign session. Campaign processing.
Check cross-domain continuity separately
Preserving the three UTM values does not prove that a journey between domains retained its user or session context. GA4 cross-domain measurement uses linker information such as _gl; redirects can remove it too. Test that configuration independently instead of treating UTM tags as a replacement for cross-domain setup. Cross-domain measurement.
What to fix after each finding
| Finding | Next action |
|---|---|
| A specific HTTP hop removes or changes planned values | Correct that redirect's query handling; repeat the same input |
| Final URL matches, collected location differs | Inspect measurement configuration, overrides and execution order |
| Intended values are collected, report looks wrong | Verify dimensions, scope, date range and processing state |
| Tags disappear only after an already measured landing | Check the original event before declaring collection failure |
| No expected debug event appears | Verify tag execution and the permitted debug/consent context |
| UTMs survive, cross-domain continuity fails | Check linker transport and cross-domain configuration |
Keep the original link, hop evidence, collected event and report view together. A useful fix explains which boundary changed the information and demonstrates that the same test now passes. Changing a redirect, seeing a 200, or watching Direct decrease is not enough on its own to establish that the campaign issue is resolved.