Skip to content
Can Elmas

Attribution · 8 min read

How to Capture UTM Parameters in Your CRM and Keep Them Past the First Page

TL;DR

UTM parameters live only in the landing page URL, so they vanish on the next click. Save them in a first-party cookie on arrival, keep separate first-touch and last-touch values, write them into hidden form fields, and map those fields to CRM contact and deal properties. Then you can report pipeline and revenue by source.

· Published · Updated

UTM parameters only exist in the URL of the page a visitor lands on, so the moment they click to a second page, the source is gone and your form captures nothing. The fix has three parts: save the values in a first-party cookie on arrival, copy them into hidden form fields at conversion, and map those fields to CRM properties that carry through to the deal. Then pipeline and revenue can be reported by source instead of guessed.

Why UTMs get lost between landing and conversion

A typical path looks like this: a LinkedIn ad sends someone to a landing page tagged utm_source=linkedin&utm_medium=paid-social. They read it, click to pricing, then to the demo page, and fill in the form. The form sits on a URL with no query string, so it has nothing to read.

Other ways the data disappears:

  • Return visits. The buyer clicks the ad, leaves, and comes back directly a week later to convert.
  • Redirects. HTTP-to-HTTPS rules, trailing-slash rules, vanity URLs and link shorteners can drop the query string before the page loads.
  • Analytics isn’t the CRM. GA4 knows the session came from LinkedIn, but it can’t tell you which named lead that was or whether their deal closed.
  • Built-in CRM tracking is coarse. HubSpot’s original and latest source properties, for example, are worth keeping, but they sort visits into HubSpot’s own categories and drill-downs, not all five of your UTM values in fields you control.

First-touch vs last-touch source fields

Store two sets of fields, because each answers a different question.

First touchLast touch
Written whenThe first visit you recordEvery new tagged or external visit before conversion
Overwrite ruleNeverYes, but never by a direct visit
AnswersWhich channels create new demand?Which channels trigger the conversion?
Tends to overcreditOrganic content and prospecting adsBrand search, email and retargeting

For each set, capture:

  • Source, medium, campaign, content and term
  • Landing page path
  • Referrer domain, so untagged organic and referral visits still get a source
  • Timestamp of the visit

Store raw values and derive the channel later in the CRM. If you store “Paid Social” instead of linkedin / paid-social, you can’t re-sort history when your channel definitions change.

The “never overwrite with direct” rule matters. Someone who clicks a Google ad and later types your URL to convert should keep Google as last touch; otherwise direct traffic swallows most of your conversions.

Persisting UTMs with first-party cookies or local storage

The logic runs on every page load:

  1. Read UTM parameters and click IDs from the URL.
  2. If no first-touch cookie exists, write one with whatever you found: UTMs, referrer, or “(direct)”.
  3. If the URL is tagged or the referrer is another domain, overwrite the last-touch cookie.
  4. Otherwise, change nothing.

A minimal version, loaded site-wide (replace example.com with your domain):

(function () {
  var KEYS = ['utm_source','utm_medium','utm_campaign','utm_term','utm_content',
              'gclid','gbraid','wbraid','fbclid','msclkid'];
  var p = new URLSearchParams(location.search), t = {};
  KEYS.forEach(function (k) { var v = p.get(k); if (v) t[k] = v.slice(0, 200); });
  var host = document.referrer ? new URL(document.referrer).hostname : '';
  var external = host && !/(^|\.)example\.com$/.test(host);
  var tagged = Object.keys(t).length > 0;
  t.landing_page = location.pathname;
  t.referrer = host || '(direct)';
  t.ts = new Date().toISOString();
  var val = encodeURIComponent(JSON.stringify(t));
  var opts = '; path=/; domain=.example.com; max-age=7776000; SameSite=Lax; Secure';
  function has(n) { return new RegExp('(^|;\\s*)' + n + '=').test(document.cookie); }
  if (!has('ft_touch')) document.cookie = 'ft_touch=' + val + opts;
  if (tagged || external || !has('lt_touch')) document.cookie = 'lt_touch=' + val + opts;
})();

Details that decide whether this holds up:

  • Cookie domain. Set it on the root domain (.example.com) so the marketing site and an app or docs subdomain share it. Separate root domains don’t share cookies at all; pass the values through link parameters instead.
  • Lifetime. The example uses 90 days. Safari caps cookies written by JavaScript to a much shorter lifetime, so if your cycle is long, set the cookie in an HTTP response from your own web server or edge layer. Safari can also cap server-set cookies from endpoints hosted apart from your site, so test it.
  • localStorage is simpler, but it belongs to one exact origin, so www and app can’t see each other’s values. It’s fine for a single-domain site.
  • Sanitize. Cap value length and never render stored values back into the page as HTML.
  • Consent. Where consent is required, load this script under the same consent category as your analytics.

Hidden fields in HubSpot, Webflow and other form tools

Every form needs hidden inputs for the fields above, plus a small script that fills them from the cookies when the form loads. Most tools pre-fill hidden fields from the query string only on the form’s own page, which is exactly the case that fails.

Form toolHow to add hidden fieldsWatch out for
HubSpot formsCreate the contact properties, then add them to the form as hidden fields; fill them from your script when the form is readyFields can only use properties that already exist, and picking a similarly named property writes to the wrong place without any error
WebflowNo dedicated hidden-field element; add hidden inputs through a code embed inside the form blockSubmissions stay in Webflow until an integration, webhook or automation pushes them to the CRM, and every field must be mapped there
Gravity Forms and similar WordPress pluginsHidden field type with dynamic population by parameter nameDynamic population reads the current URL, so fill from the cookie instead
Typeform, Calendly and other embedsDeclare hidden fields or pass parameters in the embed URLAn iframe can’t read your site’s cookies; your page must pass the values in

On the CRM side, last-touch fields can overwrite on every submission. Most form tools also overwrite existing values, so send first-touch values to staging properties and use a workflow to copy them into the permanent first-touch properties only when those are empty.

On Shopify, the order is the conversion: write the cookie values into cart attributes before checkout so they land on the order.

Capturing click IDs such as gclid and fbclid for offline conversions

UTMs tell you the campaign. Click IDs tell the ad platform which exact click became a customer, so you can send CRM outcomes back for bidding.

  • gclid is added by Google Ads auto-tagging. You may also see gbraid or wbraid on some iOS traffic, so capture those too. Google Ads only accepts offline conversions for clicks within the last 90 days, and it can also match on hashed email through enhanced conversions for leads when the gclid is missing.
  • fbclid is appended to links clicked from Facebook and Instagram. Meta’s Conversions API expects a formatted value (the fbc parameter) built from the click ID and click time, which the Meta Pixel also keeps in its _fbc cookie. Store the timestamp alongside the raw ID.
  • msclkid comes from Microsoft Advertising auto-tagging. LinkedIn and TikTok use their own identifiers; check each platform’s conversions API documentation for the parameter name and format.

Store the most recent click ID per platform on the contact and copy it to the deal. The upload side, matching stages to conversion actions and sending values, is covered in the offline conversion tracking guide.

Reporting pipeline and revenue by source

Contacts carry the source, but revenue lives on deals. Copy the source fields from the primary contact to the deal when the deal is created, not when it closes, so later touches don’t rewrite history. In HubSpot, a workflow can copy the contact’s source properties onto the associated deal when it’s created. In Salesforce, map the custom lead fields to opportunity fields in lead conversion settings.

Then add a derived channel property with explicit rules, such as google plus cpc equals Paid Search. The rules only work if tagging is consistent, which is where a shared UTM naming convention pays off.

Build one report in both views:

ChannelDeals createdPipeline valueClosed-won revenueWin rateMedian days to close
Paid Search
Paid Social
Organic Search
Email
Referral / Partner
Direct / Unknown

Run it once by first-touch channel and once by last-touch channel. A channel that ranks high on first touch and low on last touch is creating demand that other channels close; cutting it on last-touch numbers alone is how teams starve their pipeline a quarter or two later. Keep “Direct / Unknown” visible: if it’s a large or growing share of pipeline, check capture before anything else.

This is the plumbing I set up first in marketing attribution and tracking work, because no attribution model can fix source data that was never captured.

Testing and common failure points

Test every path before trusting a report:

  • Land on a tagged URL, browse three pages, submit the form: all fields populated in the CRM
  • Land tagged, leave, return by typing the URL, convert: last touch still shows the tagged visit
  • Land tagged on www, convert on a subdomain: values carried over
  • Hit each redirect and vanity URL: query string survives
  • Submit twice with different UTMs: first-touch fields unchanged
  • Create a deal: source fields copied from the contact
  • Decline consent: form still submits cleanly with empty source fields

The failures I find most often in audits:

  • Hidden fields on some forms but not others, usually the newest landing pages
  • Fields present on the form but not mapped to any CRM property
  • Imports, list uploads or enrichment tools overwriting source fields
  • Contact merges keeping the wrong record’s source
  • Single-page apps that change the URL without a page load, so the script never re-runs
  • Mixed case and spelling (LinkedIn, linkedin, li) splitting one channel into three rows

Get it built

If your CRM can’t tell you which channels produce closed revenue, I can review your source capture in a Growth Audit ($1,500 fixed, credited if you continue) and then build the fix with your team. See pricing or get in touch.

FAQ

Frequently Asked Questions

Doesn't GA4 already store UTM parameters?

GA4 reports sessions and conversions by source, but it doesn't attach that data to a named contact or to the deal that closes months later, and Google's policies prohibit sending it personally identifiable information such as email addresses. To report revenue by source, the values have to live on the CRM record.

Should I store UTMs in a cookie or in localStorage?

Use a cookie if your site spans subdomains, because a cookie can be shared across them and localStorage is tied to one exact origin. Either way, the browser is only temporary storage: write the values to the CRM on the first form submission.

How long should the attribution cookie last?

Match it to how long buyers usually take to convert, commonly 30 to 90 days. Safari caps cookies written by JavaScript at a much shorter lifetime, so if long windows matter, set the cookie in an HTTP response from the same infrastructure that serves your site.

What about leads that sales creates by hand, with no UTMs?

Give sales a required lead source picklist for manually created records and keep it separate from the automatic source fields. Mixing the two means one process overwrites the other.

Do I need cookie consent to store UTM values?

In regions that require consent, an attribution cookie usually isn't strictly necessary, so it is typically treated like analytics and set only after consent. Confirm the classification with whoever owns your privacy policy.

Work with me

Let’s find your biggest growth lever

Tell me about your growth challenge. I’ll tell you honestly if I can help — and if I can’t, who can.

  • ✓ No obligation
  • ✓ No sales script
  • ✓ Honest feedback
  • ✓ Clear next steps