Direct Traffic is HubSpot's "I don't know" bucket. When HubSpot cannot find a source for a visitor, the contact lands there. So if Direct is one of your biggest sources while you pay for Google, LinkedIn or Meta clicks, you are not looking at people typing your URL. You are looking at paid and organic visits whose source got lost on the way.
That matters because budget follows reports. If a third of your paid leads show up as Direct, every channel report under-credits paid, and the next budget decision is made on the wrong numbers.
The good news: the leak is usually in one or two places, and you can find them yourself in about 30 minutes. The Direct traffic problem page lists the 7 usual causes. This post is the hands-on version: what to open, what to type, what a bad result looks like, and in what order to run the tests.
If you are not sure what HubSpot's source properties actually record, Original Source vs Latest Source explains them first.
What HubSpot counts as Direct
HubSpot sets Original Traffic Source from the first visit it tracks for a visitor. It labels that visit Direct Traffic when there is no referring site, no UTM parameters and no other known source on it.
Three things follow from that:
- The first tracked visit decides. Not the first real visit. If HubSpot could not track the first one, the second one sets the source, and it may arrive with nothing on it.
- Tracking needs the
hubspotutkcookie. HubSpot ties page views to a visitor throughhubspotutk(visitor identity, sent with form submissions),__hstc(main tracking cookie) and__hssc(session). If the cookie is missing, blocked or reset, the history is gone. - Consent changes what counts as tracked. With an opt-in cookie banner, HubSpot only records page views after the visitor accepts. A visitor who never accepts and then submits a form is recorded as Direct Traffic.
So every cause below comes down to one question: at the moment HubSpot first saw this person, could it read the referrer and the UTMs? If not, you get Direct.
The 7 usual causes, and how each one looks in a test
For each cause I use the same test pattern: a tagged URL, a fresh email address, and a look at the resulting contact. In HubSpot, the fields to check on every test contact are Original Traffic Source (hs_analytics_source), Original Traffic Source Drill-Down 1 (hs_analytics_source_data_1), First Page Seen (hs_analytics_first_url) and the activity timeline.
1. Embedded or iframe forms lose the page context
A form inside an iframe only sees the iframe's own URL. The UTMs on the landing page stay on the landing page. The same happens with forms on a separate domain, and with visitors who land on one page and convert on another.
- Open a landing page with
?utm_source=leaktest&utm_medium=audit&utm_campaign=form1on the URL. - Right-click the form and choose Inspect. If you see an
<iframe>above the form fields, note itssrc. If the UTMs are not in it, the form cannot see them. - In the DevTools console, list the hidden fields and their values before you submit. If the form is in an iframe, switch the console context (the dropdown at the top of the Console tab) to that iframe first.
- Submit with a new address, such as
[email protected].
[...document.querySelectorAll('form input[type="hidden"]')].map((i) => `${i.name}=${i.value}`)A bad result: hidden UTM fields are empty, and the contact's First Page Seen is the iframe URL or a form host instead of your landing page. The form UTMs page covers the other ways a form drops UTMs, including custom forms that submit without hutk.
2. Redirects and link shorteners strip the query string
Every hop between the ad click and the page is a chance to lose everything after the ?. Common culprits: http to https, non-www to www, trailing slash rules, language or geo redirects, vanity URLs and link shorteners.
- Copy the real final URL from one live ad per channel, with its tracking template or final URL suffix applied.
- Run each one through curl and read every hop.
- Repeat for your most used vanity or short link.
curl -sIL "https://example.com/demo?utm_source=linkedin&utm_medium=paid_social&utm_campaign=leaktest" \
| grep -iE "^(HTTP|location)"A bad result: any location: line where the parameters are gone. For example location: https://www.example.com/demo/ with no ?utm_ after it. Everything after that hop arrives with no source. If your site redirects by language or country, run the test once more with a different Accept-Language header (-H "Accept-Language: de"), because those rules are often set up separately.
3. Cross-domain hops start a new, sourceless visit
Cookies belong to one domain. When a buyer moves from example.com to exampleapp.io or to a booking tool, HubSpot sees a new visitor unless both domains are set up. HubSpot tracks subdomains of the same root domain automatically, so www.example.com to app.example.com is fine. A different root domain is not.
- Write down every domain a buyer can pass through before converting: site, app, docs, booking tool, checkout.
- In HubSpot, open Settings > Tracking & Analytics > Tracking Code > Advanced Tracking and check each separate domain is listed.
- Click a link from your main site to the second domain and look at the URL you arrive on.
A bad result: the arrival URL has no __hstc=, __hssc= or __hsfp= parameters, or the hubspotutk value on the second domain differs from the one on the first. Booking tools that load in an iframe from their own domain cannot be fixed with link parameters at all. The cross-domain tracking page walks through each hop type.
4. Tracking code missing or loading late
One template without the tracking code, one subdomain built in a different tool, or a tag in Google Tag Manager that fires on Window Loaded instead of on page view. Any of these means the first page, the one with the UTMs, is not recorded. A JavaScript redirect that runs before the tracking code has the same effect.
- Open one page of each template type and each subdomain with a tagged URL.
- Paste the snippet below into the console on each one.
- In the Network tab, filter for
hs-scriptsand check the request appears in the first second of the page load, not after a scroll or click.
// Is the HubSpot tracking code here, and which Hub ID does it load?
[...document.scripts].map((s) => s.src).filter((src) => /hs-scripts\.com/.test(src))
// Has HubSpot set its cookies yet?
document.cookie.split('; ').filter((c) => /^(hubspotutk|__hstc|__hssc)=/.test(c))A bad result: an empty array on any page that takes traffic, or a different Hub ID on one domain. The script URL looks like js.hs-scripts.com/<hub id>.js, or js-eu1.hs-scripts.com/<hub id>.js for accounts in the EU data center.
5. The consent banner blocks the cookie before consent
This one is working as designed, but its cost is easy to miss. Under an opt-in banner, the first page view is not recorded until the visitor accepts. A visitor who accepts on page two can start with no source. A visitor who never accepts and then submits a form is recorded as Direct Traffic.
- Open a private window and land on a tagged URL. Do not touch the banner.
- Run the cookie snippet from cause 4. Note whether
hubspotutkand__hstcexist. - Submit a form without accepting. Use a new address.
- In a second private window, land on a tagged URL, click to another page, accept there, then submit with another new address.
A bad result: both contacts come back as Direct Traffic. The first one is expected under opt-in. The second one is the leak you can do something about, because the UTMs were on the URL and were simply never stored. If you also run Google Consent Mode, the Consent Mode service explains how the two settings interact.
6. Safari limits cookie lifetime
Safari's tracking prevention deletes cookies set by JavaScript after 7 days without the visitor interacting with your site, and after 24 hours when the visitor landed from a link-decorated URL, such as an ad click with a click ID (WebKit tracking prevention). The HubSpot cookies are set by JavaScript. A buyer who reads your ad landing page on Monday and comes back to book a demo the next week can look like a new visitor.
- Filter your Direct Traffic contacts from the last 90 days and add the Number of Pageviews column.
- Count how many converted on their first tracked page view with no earlier history in the timeline.
- Ask sales how long a typical buyer takes from first visit to demo request.
A bad result: many Direct contacts whose timeline starts with the form submission, in a market where buyers take more than a few days to decide. That pattern fits a cookie that expired between visits. It also fits causes 5 and 7, so read it together with those tests.
7. Ad and script blockers
Blockers that stop the HubSpot tracking script mean no page views are recorded. The form may still submit, so the contact gets created with little or no source data.
- Install a common blocker, such as uBlock Origin, in a separate browser profile.
- Land on a tagged URL, open the Network tab and filter for
hs-scripts. Note whether the request is blocked. - Submit the form with a new address and check the contact.
A bad result: the tracking script is blocked, the form still works, and the contact comes back as Direct Traffic with no page views before the submission.
The 30-minute self-audit
Run the tests in this order. It starts with your own data, so you know where to look, then goes from the cheapest tests that cover the most paid traffic to the edge cases. Times are rough. Prepare a short list of new email addresses before you start.
- Read the Direct contacts (5 minutes). In Contacts, filter Original Traffic Source is Direct Traffic and Create date is in the last 90 days. Add the columns Original Traffic Source Drill-Down 1, First Page Seen and Number of Pageviews. Sort by Drill-Down 1, which holds the entrance URL for Direct contacts. Look for a pattern: one landing page, one form, one domain. That pattern tells you which test to run first.
- Redirects (5 minutes). Run one live ad URL per channel through
curl -sIL(cause 2). Mark any hop that drops the parameters. - Forms (7 minutes). Submit each form type once from a tagged URL: the main embedded form, any pop-up, any form on a second domain. Check hidden field values before each submit (cause 1).
- Tracking code and domains (4 minutes). Run the script and cookie snippet on each template and domain that takes traffic. Click one cross-domain link and check the arrival URL (causes 3 and 4).
- Consent (4 minutes). Run the two private window tests from cause 5.
- Safari and blockers (3 minutes). Repeat one tagged test in Safari and one with a blocker on (causes 6 and 7).
- Write it down (2 minutes). Give HubSpot a few minutes to process the submissions, then write one line per test: what you did, the test email, the source the contact got.
Every test contact that came back as Direct Traffic, or with the wrong drill-down, is a leak.
What a proper fix looks like
Patching the one form that failed helps, but the leak tends to come back with the next site change. A durable fix has four parts.
- Capture. Read UTMs and click IDs from the landing URL on every page that can receive traffic, before any redirect or router cleans them. This includes
gclidfor Google Ads. - Persist. Store the first-touch values in a first-party cookie or local storage, so a visitor who lands on one page and converts on another still carries them. For Safari, a cookie set by your own server on your own domain survives longer than one set by JavaScript, but only if that server is not a CNAME to a third-party host. That is one reason server-side tracking is set up on your own infrastructure.
- Pass to the CRM. Write the stored values into hidden fields on every form, and send
hutkwith any custom or API form, so the contact record gets both the UTMs and the browsing history. - Feed back to the ad platforms. Once the source and click ID sit on the contact, send qualified leads and closed deals back to Google Ads, Meta and LinkedIn, so their bidding learns from pipeline instead of form fills. That is the offline conversions work.
If your test showed one clear leak, such as an embedded form that drops UTMs, that is typically 2 to 5 hours of work as part of The Fix. If several tests failed, or you could not tell why, the Attribution Audit maps every leak with evidence and a fixed-price plan in 5 business days from confirmed access.
When to get help
Run the 30 minutes yourself first. If every test passes and Direct is still high, the cause is likely in a part of the journey you did not test, such as a booking tool, an app sign-up or a chat widget. If tests fail in code you do not own, or you fixed a leak once and it came back, that is the point to bring someone in.