Loading tutorials…
Loading tutorials…
The HubSpot tracking code is what makes every other piece of Marketing Hub work — forms, workflows, lifecycle stages, lead scoring. Get this wrong and half of HubSpot quietly stops attributing what it should. Here's the install path most guides skip.
Who this is forOwners and ops leads who just bought Marketing Hub (Starter, Pro, or Enterprise) and need tracking live before they build anything else. Also: anyone whose 'Sources' report shows 80% Direct traffic — that's almost always a tracking-install issue.
What you'll need
Step 1
Find the unique tracking snippet inside HubSpot Settings. Every portal has its own Hub ID — using the wrong one breaks attribution.
Click the Settings (gear icon, top-right) inside your HubSpot portal.
In the left sidebar, go to Tracking & Analytics → Tracking Code.
Copy the JavaScript snippet shown. It looks like `<script ... src="//js.hs-scripts.com/XXXXXXXX.js">` where XXXXXXXX is your Hub ID.
Note your Hub ID separately — you'll reuse it for forms embeds, chatflows, and any custom event firing.
Do NOT paste this directly into your theme files. We will install it via GTM in the next step so you can manage it without code changes later.
Step 2
Create a Custom HTML tag in GTM, paste the snippet, and fire it on All Pages. This is the cleanest install path.
Open Google Tag Manager → your container → Tags → New.
Name the tag "HubSpot Tracking Code" (the name matters for the audit step later).
Tag Configuration → Custom HTML. Paste the HubSpot snippet from step 1.
Triggering → All Pages (Page View). For SPAs (React, Vue, single-page apps), switch this to "History Change" so route changes re-fire the code.
Advanced Settings → Tag firing options → Once per page. This prevents double-counting if other tags trigger it.
Save the tag. Submit and publish the container.
Step 3
Open your site, check Network panel for `js.hs-scripts.com`, and confirm `_hsq` is defined in the console.
Open your site in an incognito window. Open DevTools → Network tab.
Filter for "hs-scripts" — you should see `XXXXXXXX.js` load with status 200.
Switch to the Console tab. Type `_hsq` and press enter. You should see `Array(0)` or similar, not "ReferenceError."
Type `_hsq.push(["doNotTrack"])` — if HubSpot is loaded, this won't error.
Navigate to a few different pages. The hs-scripts request should re-fire (or for SPAs, the `_hsq.push(['setPath', ...])` calls should fire) on every route change.
Step 4
Inside HubSpot, the Tracking URL Builder + Sources report should reflect your test visits within 30 minutes.
Inside HubSpot, go to Reports → Analytics Tools → Traffic Analytics → Sources.
Set the date range to "Today." If your test visit was within the last 30 minutes, you should see at least 1 Direct or Organic Search visit reflected.
Cross-check via Contacts → All Contacts → filter by "Time first seen" = Today. Your test sessions (anonymized) will show as visitor records.
If neither shows data after an hour, the script loaded but events aren't reaching HubSpot. Most common culprit: a Content Security Policy (CSP) on your site blocking `script-src` for `js.hs-scripts.com` and `js.hs-analytics.net`.
Step 5
The tracking code only knows who someone is *after* they share an email. Identify them earlier with `_hsq.push(["identify", { email: ... }])`.
By default, HubSpot only ties browsing to a contact after a form submission, email click, or chatflow interaction. Anything before that is anonymous and gets stitched retroactively.
If you have logged-in users on your site (app, customer portal, dashboard), fire an identify call on every page they load: `_hsq.push(["identify", { email: user.email, id: user.id }]);`
This must run BEFORE the `trackPageView` call. The typical pattern: identify in your app shell, then push a `trackPageView` on every route change.
Verify by visiting a logged-in page, then checking Contacts → that user → Recent activity. Pageviews should now attribute correctly instead of showing as anonymous.
Step 6
Write down the Hub ID, the GTM tag name, the trigger config, and any CSP exemptions you added. Future-you will thank present-you.
In a shared doc (Notion, README, anywhere your team can find it), document: HubSpot Hub ID, GTM tag name + trigger, any CSP entries, identify-call locations, and your verification date.
This becomes critical when you migrate themes, redesign the site, or onboard a new ops person. Without it, the next person assumes nothing is installed and re-installs — leading to double-firing.
Specialists always do this. It's the single fastest tell of a professionally-managed HubSpot install.
Common mistakes
Hard-coding the snippet into theme files instead of GTM
What goes wrong: When you change themes, migrate to a new CMS, or do a redesign, the snippet gets dropped and nobody notices for weeks. Sources data goes 100% Direct. Workflows that depend on page-view triggers stop firing.
How to avoid: Install via Google Tag Manager (or your equivalent tag platform). The tag survives theme changes, redesigns, and CMS migrations as long as GTM is installed.
Installing the tracking code twice
What goes wrong: Double-installs are common: GTM fires it once, the HubSpot WordPress plugin fires it again, or a developer hard-coded it months ago and forgot. Pageviews double-count. Bounce rate drops to 5%. Time-on-site doubles. Every metric is silently wrong.
How to avoid: In DevTools Network, search for "hs-scripts." If you see two requests on the same page load, you have a duplicate. Remove all installs except the GTM one. Wait 24 hours for data to settle.
Ignoring SPA route changes
What goes wrong: On a Next.js or React app, you record 1 pageview per session instead of 1 per page. HubSpot thinks all your traffic bounces after one page. Workflows triggered by 'viewed page X' never fire even when users do visit X.
How to avoid: Use a GTM History Change trigger and push `_hsq.push(['setPath', window.location.pathname])` + `_hsq.push(['trackPageView'])` on every route change. Most React Router and Next.js setups have a standard pattern for this.
CSP headers blocking HubSpot domains
What goes wrong: Tracking script never loads. Console fills with CSP errors. HubSpot thinks you have no visitors. You're paying $890/mo for Marketing Hub Pro and getting zero attribution data.
How to avoid: Add to your CSP: `script-src 'self' *.hs-scripts.com *.hs-analytics.net *.hubapi.com; connect-src 'self' *.hubapi.com *.hubspot.com; frame-src *.hsforms.net`. Test with DevTools Console open.
Skipping the identify() call for logged-in users
What goes wrong: All in-app activity is anonymous. Lead-scoring rules that depend on 'viewed pricing 3x' never trigger because pricing visits aren't tied to a contact. Sales handoff data is incomplete.
How to avoid: Fire `_hsq.push(["identify", { email, id }])` in your app shell for any logged-in user. Verify by spot-checking 3 known users in Contacts → Recent activity.
Not verifying after install
What goes wrong: You install the snippet, see no errors, and assume it works. Three weeks later you notice Sources data is empty and now you're debugging a tracking issue with three weeks of bad data baked in.
How to avoid: Always verify: Network tab shows hs-scripts load, Console shows `_hsq` defined, HubSpot Sources report shows your test visit within an hour, and a test contact shows pageviews in Recent activity.
Recap
Done — what's next
How to set up HubSpot contact properties without making a mess
Read the next tutorial
Hand it off
Installing the snippet is the easy part. Making sure every page, every SPA route, every logged-in session, and every CSP-protected domain is actually feeding HubSpot is what takes a specialist's eye. A vetted HubSpot specialist can audit your install + fix any gaps for $80-160 total, or set you up with ongoing ops support at $400-1,200/mo at $14-16/hr.
See specialist rates
No. The tracking code works on all HubSpot tiers including the free CRM. What changes with paid tiers is what HubSpot does with the data — workflows, lead scoring, and advanced reports require Marketing Hub Starter or above.
If you're WordPress-only and don't use GTM for anything else, the plugin is fine. If you use GTM for any other tag (GA4, Meta Pixel, anything), centralize on GTM — running both tends to cause duplicate installs.
Pageviews appear in Sources within 30 minutes. New contact records appear within an hour. Sources attribution stabilizes over 7-14 days as visitors return and HubSpot stitches anonymous sessions to contacts.
The HubSpot script is loaded async and runs around 35-45KB gzipped. On a healthy site it adds 10-30ms to TTI. If your Core Web Vitals tank after install, you likely have a render-blocking tag misconfig, not the HubSpot script itself.
GA4 is for traffic and behavior analytics. HubSpot tracking is for attributing visits to specific contacts in your CRM so workflows can react. They serve different purposes — run both. The HubSpot install does not replace GA4.
HubSpot Marketing Hub
Properties are the foundation under every workflow, segment, and report in HubSpot. Most owners create 80+ custom properties in the first month, then spend the next year cleaning up the mess. Here's the discipline to do it right the first time.
HubSpot Marketing Hub
Forms are where every contact in HubSpot starts. The default form builder is good — it gets better when you use smart fields, conditional logic, and behavior-triggered popups. Here's the setup that lifts form conversion 30-50% over the out-of-the-box defaults.
HubSpot Marketing Hub
Lifecycle Stage is the most misunderstood property in HubSpot. Set it up wrong and marketing reports show 'MQLs created' that sales never touches. Set it up right and the whole pipeline becomes legible. Here's the taxonomy and automation that holds up.
HubSpot Marketing Hub
HubSpot is one of the most powerful platforms in marketing — and one of the easiest to over-configure into uselessness. Here's the honest framework: when DIY costs you more than hiring, and how to tell which side of the line you're on.