Published by Terminus, the marketing taxonomy governance platform.

HubSpot UTM Tracking: The Definitive 2026 Guide

PC

Puru Choudhary

Last updated

HubSpot ships with one built-in attribution property (Original Source), and that property does most of what marketing teams ask of it on day one. The trouble starts in month three, when paid media wants channel-level reporting, RevOps wants first-touch versus last-touch, and the CFO wants marketing-sourced revenue that ties back to a specific campaign. None of that lives inside Original Source alone. You build it by capturing UTM parameters into custom contact properties, wiring those properties to forms and workflows, and choosing an attribution model that matches how your team actually buys media.

Published by Terminus, the marketing taxonomy governance platform.

TL;DR

  • HubSpot’s built-in Original Source classifies the channel a contact came from but does not store the raw utm_source, utm_medium, or utm_campaign values. Build custom contact properties to keep those.
  • First-touch and last-touch capture are different properties with different update behaviour. You almost always want both.
  • Original Source can be overwritten by a CSV re-import, by the HubSpot Forms API setting it directly, or by certain Marketing Hub integrations. Lock it down before you trust it.
  • HubSpot’s multi-touch revenue attribution reports are Marketing Hub Enterprise only as of 2026. Professional gives you the contact-create attribution surface and standard source reports, but the multi-touch model selector sits on Enterprise. Plan tier accordingly.
  • Garbage in, garbage out. A utm_source=Facebook and a utm_source=facebook are two channels in HubSpot reports. Governed UTM values fix this at the source.
  • To move HubSpot attribution data into a warehouse, use Operations Hub’s data sync, the CRM API, or a reverse ETL tool like Hightouch or Census.

HubSpot’s built-in Original Source: what it captures, what it does not

Original Source is the HubSpot default contact property that classifies how a contact first arrived. It is populated automatically by the HubSpot tracking code (__hstc cookie plus the page-load script) and it is sticky: once set on a contact, HubSpot does not change it on subsequent visits unless a downstream action forces it.

The property is actually a triple. The visible field is Original Source, a high-level enum. Underneath it sit two drill-downs: Original Source Drill-Down 1 and Original Source Drill-Down 2. For a Google Ads visit, Original Source resolves to “Paid Search”, Drill-Down 1 holds the source domain or platform (google), and Drill-Down 2 holds the utm_content value when one is present. The exact mapping of Drill-Down 2 varies by channel: for Paid Search and Paid Social it typically captures utm_content; for some referral channels it captures the campaign name. Check the channel-specific behaviour in HubSpot’s docs rather than assuming.

The high-level Original Source values in 2026 are Organic Search, Paid Search, Email Marketing, Organic Social, Paid Social, Referrals, Other Campaigns, Direct Traffic, Offline Sources, and Other. The “Other Campaigns” bucket is where HubSpot puts visits that carry UTM parameters but do not match an obvious paid-search or paid-social pattern. The mapping rules are not fully published, which is why teams that need precise channel definitions roll their own.

What Original Source does NOT do

It does not preserve the raw utm_source, utm_medium, utm_campaign, or utm_term strings on their own. Drill-Down 2 captures utm_content for many channels (and the campaign name for some others), but the full UTM tuple is not retained in the Original Source family. It does not track changes over time: Original Source is first-touch by definition, and the built-in “Latest Source” overwrites itself on every new session with the same channel-bucket limitations. It does not attribute revenue on its own; the multi-touch attribution machinery in Marketing Hub Enterprise relies on a separate attribution model selection set per report. And it does not survive a sloppy import: a CSV with a populated Original Source column overwrites what the tracking code captured.

The practical takeaway: treat Original Source as a useful, opinionated channel summary. It is the floor of your attribution stack, not the ceiling.

Custom contact properties for UTM persistence

The pattern that works across HubSpot accounts in 2026 is twelve custom contact properties: six for first-touch and six for last-touch. Some teams add a thirteenth and fourteenth for the timestamp of each touch.

Property name (internal)Field typeUpdate behaviour
first_touch_utm_sourceSingle-line textSet once, never overwritten
first_touch_utm_mediumSingle-line textSet once, never overwritten
first_touch_utm_campaignSingle-line textSet once, never overwritten
first_touch_utm_contentSingle-line textSet once, never overwritten
first_touch_utm_termSingle-line textSet once, never overwritten
first_touch_utm_idSingle-line textSet once, never overwritten
last_touch_utm_sourceSingle-line textOverwrites on each new submission
last_touch_utm_mediumSingle-line textOverwrites on each new submission
last_touch_utm_campaignSingle-line textOverwrites on each new submission
last_touch_utm_contentSingle-line textOverwrites on each new submission
last_touch_utm_termSingle-line textOverwrites on each new submission
last_touch_utm_idSingle-line textOverwrites on each new submission

Create these under Settings > Properties > Contact properties > Create property, pick the contact object, and group them under a custom group called “UTM attribution” so they stay together in the UI.

In HubSpot’s property settings, you cannot natively mark a property as “write once.” Instead, implement the constraint in a workflow that only sets the first-touch property if it is currently empty: trigger on form submission, branch on whether first_touch_utm_source is unknown, set the value if so, and repeat for the other five first-touch properties. Last-touch is simpler: every form submission sets the last-touch properties unconditionally, because by definition the last touch is whatever just happened.

Worked example: governed UTM values feeding HubSpot

Where this often breaks is data quality at the source. A team running paid social, paid search, email, and partner campaigns can end up with multiple variants of “facebook” in first_touch_utm_source: Facebook, facebook, FB, fb, META, meta, facebook.com. Each variant becomes its own row in a HubSpot pivot. The marketing-sourced revenue report then shows nine percent of revenue from Facebook and seven percent from facebook, and nobody knows whether to add them or pick one. (Illustrative figures for the framework.)

The fix is upstream of HubSpot. Governed UTM values, generated from a controlled vocabulary before the link is published, are what keep utm_source consistent. Terminus is built for this: every campaign builder picks from a fixed list, validation rejects free-text typos, and the values that land in HubSpot are the values you intended. The HubSpot side stays simple. The work goes where it belongs, at the moment of link creation.

Form field mapping

HubSpot offers three form pathways, and the UTM capture story is different for each.

HubSpot Forms (native)

Native HubSpot forms have first-class support for hidden fields that read from query-string parameters. In the form editor, add a hidden field for each UTM property, and in the field settings under “Default value”, select “URL parameter” and enter the parameter name.

The pattern:

  • Add hidden field first_touch_utm_source → default value from URL parameter utm_source
  • Repeat for the five other parameters
  • Repeat for the six last-touch properties

HubSpot reads the parameter from the page URL at the moment the form loads, then writes the captured value to the contact property on submission. The cookie-based persistence is handled by the HubSpot tracking script (__hstc), which retains UTM values across the contact’s session. If your visitor lands on /pricing?utm_source=google&utm_medium=cpc, navigates around, and submits a form on /demo ten minutes later, HubSpot still has the UTMs.

Embedded HubSpot forms on non-HubSpot pages

The same form, embedded with the standard <script> snippet on a non-HubSpot page, works the same way for query-string reading. The script runs against the parent page’s URL, not against the form iframe’s URL. This relies on the HubSpot tracking code (the page-load script) also being installed on the host page. Without it, HubSpot has no __hstc cookie, no session persistence, and only the values present in the URL at form load. The fix is to install the tracking script alongside the form embed.

Non-HubSpot forms posting to the API

Custom-built forms, single-page apps, and forms on platforms like Webflow or Framer can write contact properties directly using the HubSpot CRM API. The relevant endpoint is POST /crm/v3/objects/contacts (or PATCH for updates). You pass the UTM values in the properties object of the request body:

{
  "properties": {
    "email": "lead@example.com",
    "first_touch_utm_source": "google",
    "first_touch_utm_medium": "cpc",
    "first_touch_utm_campaign": "2026-q3-demand-gen",
    "last_touch_utm_source": "linkedin",
    "last_touch_utm_medium": "paid-social",
    "last_touch_utm_campaign": "2026-q3-demand-gen"
  }
}

The API does not enforce the “first-touch is sticky” semantics for you. The calling code has to fetch the existing contact, check whether first_touch_utm_source is already populated, and only write if it is empty. The same workflow rule that protects native HubSpot form submissions does not run for direct API writes unless you trigger it explicitly.

A common pattern: send the API call with last-touch fields only, then let a HubSpot workflow handle the first-touch logic on the property-change trigger. That keeps the conditional logic in one place.

Workflow automation: lead source from UTMs, lifecycle stage triggers

Once UTM values are landing in contact properties, you can use them to drive workflow logic.

Setting Lead Source from UTMs

HubSpot has a “Lead Source” property by default, but its values are limited and it is not always populated automatically. Many teams convert it to a custom dropdown with controlled values like paid-search, paid-social, organic-search, organic-social, email, direct, referral, event, partner.

A workflow can populate Lead Source from first_touch_utm_medium: branch on cpc to set paid-search, on paid-social or cpm to set paid-social, on email to set email, and surface unmatched cases to the RevOps queue. This kind of mapping gets brittle when utm_medium values are inconsistent. If three different campaigns send cpc, ppc, and paidsearch, the workflow needs three branches and someone has to maintain them. Governed UTM values at source eliminate the maintenance.

Lifecycle stage triggers

Lifecycle transitions are usually driven by behaviour (form submissions, demo bookings, opportunity creation) rather than by UTMs directly. Where UTMs help is in segmenting which lifecycle behaviour to weight more heavily: a workflow that promotes contacts to MQL on demo-request form submission can branch on whether last_touch_utm_campaign matches a high-intent campaign ID and route the high-intent path to a dedicated SDR queue. This is also where utm_id becomes useful. If your team is already using a campaign-ID scheme (HubSpot Campaigns object IDs, or your own), utm_id becomes the join key between paid spend and pipeline.

Marketing-sourced revenue reports

HubSpot’s revenue attribution reporting lives in Reports > Attribution reports. As of 2026, the multi-touch attribution model selector is gated to Marketing Hub Enterprise (and the Marketing+ bundle). Professional includes contact-create attribution and the standard source reports but not the multi-touch model selector; Starter and Free Tools are below that line.

The attribution models HubSpot supports on Enterprise include First Interaction (100% credit to first touch), Last Interaction (100% to last touch), Linear (equal across all touches), U-Shaped (40% first / 40% lead conversion / 20% split across middle touches), W-Shaped (30% first / 30% lead conversion / 30% opportunity creation / 10% across middle touches), Time Decay (recent touches weighted more heavily), and Full Path (similar to W-Shaped with a customer-close touchpoint added). HubSpot has added and retired model variants over time, so check the current list in your account before treating any name as canonical.

The model is selected per report, not globally. You can have a First Interaction report and a U-Shaped report side by side, and HubSpot will compute both from the same underlying contact-interaction data.

Where UTMs feed the report

The interactions HubSpot weighs are page views, form submissions, email opens and clicks, and (with the right integrations) CRM activities. Each inherits the channel and source assignment from the contact’s UTM history. If your UTM properties are clean, the attribution report groups correctly. If they are inconsistent, the report fragments revenue across spurious channel-source combinations.

The custom report builder (Reports > Custom report builder) extends this. You can build a report that joins the Deal object, the associated Contact roster, and your custom UTM properties, then aggregate closed-won revenue by first_touch_utm_campaign. This is the report your CFO will eventually ask for.

A practical caveat: the standard attribution reports use HubSpot’s internal channel classification (the Original Source enum). The custom report builder lets you slice by your own utm_* properties. The two numbers will not exactly match, because HubSpot’s enum bucketing differs from your raw UTM values. Pick one as your source of truth and stick with it.

Exporting to a warehouse

For teams running BI on top of Snowflake, BigQuery, Redshift, or Databricks, three pathways move HubSpot attribution data into the warehouse.

HubSpot CRM API

The CRM API is the direct route. Relevant endpoints include GET /crm/v3/objects/contacts with the properties parameter listing your UTM property internal names, GET /crm/v3/objects/deals with associations=contacts to pull deal-contact relationships, and GET /crm/v3/objects/deals/{dealId}/associations/contacts for the contact roster on a single deal. The API has rate limits that vary by HubSpot plan, and for larger accounts the search endpoint with batched results and cursor-based pagination is more efficient than per-record GETs.

Authentication uses Private Apps in 2026: you generate an app under Settings > Integrations > Private Apps, grant the scopes (crm.objects.contacts.read, crm.objects.deals.read), and authenticate with the bearer token. Legacy hapikey auth was deprecated and removed in earlier updates.

Operations Hub data sync

Operations Hub (Starter and above) ships native data sync to Snowflake, BigQuery, and other destinations. The setup is configuration-driven inside HubSpot: pick the destination, map the contact and deal properties, and let HubSpot push incremental updates. The sync runs on a schedule (typically every 15 minutes for paid tiers), and it handles property changes including additions and renames.

The tradeoff is that Operations Hub is a separate line item on the HubSpot bill, and the warehouse destinations included in the base tier are limited. Snowflake is usually included; BigQuery and Databricks may require higher tiers.

Reverse ETL tools (Hightouch, Census)

Hightouch and Census both treat HubSpot as a source. They connect via the HubSpot CRM API, sync contacts and deals to your warehouse, and handle the rate-limit and pagination concerns. They are often the right answer when your warehouse is the source of truth, when you need bidirectional sync (warehouse to HubSpot for enrichment, HubSpot to warehouse for reporting), or when Operations Hub data sync does not cover your destination. Pricing for both is tier-based and changes regularly. Cite the current pricing pages rather than memorising a number.

Once data is in the warehouse, first-touch attribution in SQL is a join from contacts to deal-contact associations to deals, filtered to closed-won and grouped by first_touch_utm_campaign and first_touch_utm_source. Last-touch swaps the property prefix. Linear and U-shaped require an interactions table, which is what the HubSpot CRM API’s Engagements endpoints provide.

Common pitfalls

The Original Source clobber

The most-asked HubSpot attribution question is “why did my contact’s Original Source change?” The honest answer is that Original Source can be overwritten in more cases than the documentation makes obvious. A CSV import that includes the Original Source column writes those values directly. Some integrations (notably the Salesforce sync, when Salesforce Lead Source is mapped to HubSpot Original Source) push values back. Re-merging contacts can promote the surviving contact’s Original Source to whichever value HubSpot’s merge logic picks, which is not always the older one. The hs_analytics_source family is generally read-only via the public v3 CRM API (the values are managed by HubSpot’s analytics layer); attempts to write to it via the API are typically ignored, though behaviour at the edges varies by integration path, so test against your own sandbox before assuming either direction.

If Original Source is load-bearing for your reporting, lock it down. The pattern that works: create a custom property original_source_stable, run a workflow that copies Original Source to that property the first time it is populated, and use original_source_stable (not the native Original Source) in your reports. If HubSpot’s value changes later, you still have the original preserved. This is a workaround, because you cannot make HubSpot’s native Original Source immutable.

Attribution model mismatch

Two reports that should agree often disagree because they use different attribution models. The HubSpot dashboard widget might default to First Interaction; an analyst’s custom report might default to Linear. Both are “marketing-sourced revenue,” but they answer different questions. The remedy is to standardise on one or two models for executive reporting and to label every report with the model it uses.

For B2B SaaS in 2026, practitioner consensus has drifted toward First Interaction or U-Shaped as the primary model, with W-Shaped used when marketing is also accountable for SQL-stage influence. Last Interaction is a poor fit for B2B because it credits the bottom-of-funnel content that closes a deal, not the demand-gen work that opened it.

HubSpot’s tracking code respects the consent banner if you configure it to. The HubSpot Cookie Consent Banner is available across all tiers, including free CMS tools, and by default in 2026 it blocks the tracking script until consent is given in EU regions. If the visitor declines, the __hstc cookie is never set. UTM values on the URL at form submission are still captured (because the form reads them from the page URL directly) but the persistence across pages does not happen. The result is an attribution gap for non-consenting EU visitors. The right answer depends on your legal posture: standard banner for most teams, custom consent flow for stricter regulated sectors.

The taxonomy-drift problem

A typical HubSpot account that has run paid acquisition for a year or two will accumulate dozens of variant utm_source values, many of them spelling variations of the same actual source. The attribution report fragments. Workflow logic gets layered with OR branches. RevOps cleans it up once a quarter, then it drifts again because no one owns the source-of-truth vocabulary.

This is not a HubSpot problem. HubSpot stores what you send. The fix is upstream: a campaign-builder discipline that constrains utm_source, utm_medium, utm_campaign, and utm_content to a governed vocabulary before the link is published. That is the entire reason Terminus exists, and it pays back disproportionately in HubSpot reporting clarity.

FAQ

Does HubSpot automatically capture UTM parameters?

Partially. The HubSpot tracking code parses utm_source, utm_medium, and utm_campaign from the page URL and uses them to populate the Original Source property and its drill-downs. The raw values are not retained on the contact unless you also create custom properties and a form-field mapping that writes them.

What is the difference between Original Source and Latest Source in HubSpot?

Original Source is the channel HubSpot assigned the first time the contact was identified. It is sticky by default. Latest Source is the channel of the most recent session that HubSpot tracked. Latest Source overwrites itself on each new session. Both use HubSpot’s internal channel bucketing, not the raw UTM values.

Can Original Source be changed after it is set?

Yes, in several cases. A CSV import including the Original Source column will overwrite it. Some HubSpot integrations, notably the Salesforce connector when Lead Source is mapped, can push values back. The hs_analytics_source family is generally read-only via the public v3 CRM API (HubSpot manages it from the analytics layer) but edge cases vary by integration path; test in your sandbox. To preserve the original value, copy it to a custom locked property the first time it is populated and use that property in reports.

How do I capture UTMs from a non-HubSpot form?

If the form posts to the HubSpot Forms API, include UTM values as hidden fields. If it posts to the HubSpot CRM API directly, include the UTM values in the properties object of the contact payload. Pair this with a HubSpot workflow that enforces first-touch stickiness, since the API does not enforce that for you.

Does HubSpot’s marketing-sourced revenue report use first-touch or last-touch?

Neither, by default. The attribution model is selected per report, and the multi-touch model selector requires Marketing Hub Enterprise as of 2026. The supported models include First Interaction, Last Interaction, Linear, U-Shaped (40/40/20), W-Shaped (30/30/30/10), Time Decay, and Full Path. The standard dashboard widgets often default to Linear or First Interaction depending on the widget. Check each report’s settings (and your tier) before treating any number as authoritative.

Why are my UTM values inconsistent in HubSpot reports?

Because HubSpot stores exactly what is in the URL. Facebook and facebook are two distinct values in HubSpot’s reporting tables. The fix is to govern UTM values at the moment the link is created, using a controlled vocabulary and validation. Tools like Terminus enforce this upstream so HubSpot only ever sees the values you intended.

What attribution model should I use in HubSpot for B2B SaaS?

For most B2B SaaS teams in 2026, First Interaction (for demand-gen credit) and U-Shaped (for balanced funnel credit) are the two most useful models. Last Interaction is generally a poor fit for long sales cycles because it overweights bottom-of-funnel content. Run two reports, one with each model, and compare. The gap between them is informative.

How do I move HubSpot attribution data to a warehouse?

The HubSpot CRM API gives direct programmatic access via a Private App (rate-limited). Operations Hub data sync pushes to supported destinations on a schedule. Reverse ETL tools like Hightouch and Census handle the API, pagination, and incremental sync for you, and are usually the right choice when the warehouse is your source of truth.

It can. If the HubSpot tracking script is gated behind a consent banner and the visitor declines, the __hstc cookie does not get set, so UTMs do not persist across pages. UTMs on the URL at form submission are still captured because the form reads them from the URL directly. The result is an attribution gap for non-consenting visitors.

Can I use utm_id to tie HubSpot deals back to paid-spend data?

Yes, and it is the cleanest pattern. Pass a stable campaign ID in utm_id on every paid link, capture it into a custom HubSpot property, and join it to your spend data in the warehouse on the same key. HubSpot’s Campaigns object also has an internal ID you can use if you prefer HubSpot-native IDs, but utm_id works better when your spend data lives outside HubSpot.


Last updated: 25 June 2026.

Terminus helps you and your team be consistent in UTM tracking

Try Terminus risk-free for 21 days. Cancel anytime with 1 click.