> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiveku.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How Attribution Works

> What Hiveku captures when someone arrives from an ad, how a lead is tied back to the click that produced it, why our numbers legitimately differ from the ad platform's, and how to tell a real discrepancy from an expected one

Every attribution argument is the same argument: the platform says one number, the CRM says a smaller one, and nobody can explain the gap. This page explains what Hiveku actually records, what it can prove, what it can only infer, and how to read the difference.

The short version: Hiveku captures attribution at the moment of the visit, classifies it once at ingest, stores the evidence alongside the verdict, and refuses to guess when it does not know. Most of the design decisions below exist to keep a wrong answer from looking like a confident one.

## What gets captured, and when

When someone lands on a tracked site, the analytics embed looks at the URL for two kinds of thing:

* **UTM parameters** — `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`
* **Ad click IDs** — the opaque token an ad platform appends to the landing URL. Google (`gclid`, plus `gbraid` and `wbraid` for app and web-to-app journeys), Meta (`fbclid`), Microsoft (`msclkid`), TikTok (`ttclid`), X (`twclid`) and LinkedIn (`li_fat_id`).

A click ID is the strongest signal there is. UTMs are authored by whoever built the link and can be wrong, missing, or copied between campaigns. A click ID is minted by the ad platform for one specific click, which is what makes it the only thing that can be matched back with certainty.

Anything captured is kept as **first touch** for 90 days.

<Note>
  First touch means first. If a visitor arrives from a newsletter in January and clicks an ad in March, the stored campaign is still January's newsletter — it is never overwritten. This matters more than it sounds; see [When the stored campaign is not this lead's campaign](#when-the-stored-campaign-is-not-this-leads-campaign).
</Note>

Alongside the parameters, the embed records **when they were captured** — the instant the ad click landed. That timestamp is what makes it possible to test a lead against an ad platform's window rather than guessing.

<Warning>
  This timestamp ships from analytics embed **v3.4** onward. It is absent from older cached copies of the embed — still most live traffic — and from three ingest paths that carry no visitor timeline at all: the storage-free capture module, inbound webhooks, and hosted forms. Treat a dated click as a bonus on the records that have one, not as something every lead carries.
</Warning>

## Where attribution can survive

The same visit can leave attribution in more than one place, and they are not equally reliable. Hiveku reads them in a fixed order of preference:

<Steps>
  <Step title="The durable first-touch store">
    Kept in the browser for 90 days. Survives return visits, so a lead who converts three weeks after clicking still carries the campaign. Empty in private browsing, when a consent banner blocks analytics, or when local storage is restricted.
  </Step>

  <Step title="The landing URL">
    The address the session began on. Recorded with the session, so it still carries the campaign in the very common case where the 90-day store is empty but the visit itself is intact. Only describes *this* visit.
  </Step>
</Steps>

<Note>
  There is a third fallback in the code — the URL of the page the form sat on — but today's embeds send that as a bare path with the query string already stripped, so in practice it never carries a click ID. Do not rely on it.
</Note>

UTMs are taken **all-or-nothing from one source**, never merged. Mixing a `utm_source` from the store with a `utm_campaign` from the URL would invent a source/campaign pair that never existed and matches nothing in the platform's reporting.

Click IDs are searched across every source, which is why a paid click can still be identified as paid even when browser storage was empty. This is a common real-world case: storage blocked, so the referrer was the only clue, and a Google Ads click referred by google.com looks exactly like an organic search — until the `gclid` sitting in the landing URL is found.

## Where the verdict is stored

Channel is classified **once, at ingest**, and written onto the record along with the evidence it was derived from: the UTMs, the click ID, the referrer, the landing page, the page path, and the session.

Two consequences worth knowing:

* **The verdict travels with the record.** Reports do not re-derive the channel every time they are read, so a change in classification logic does not silently restate last quarter.
* **The evidence travels too.** You can always ask *why* a lead is attributed the way it is, rather than being handed a label to take on faith.

Records created before channel was stored are classified on read instead, and are marked as derived so the two are never confused.

## Knowing versus inferring

This is the part that matters most, and the part most tools blur.

Hiveku records not just *when* the first touch was, but **how it knows** — because the three ways of knowing are not equally strong:

| Provenance           | What it means                                 | What it proves                               |
| -------------------- | --------------------------------------------- | -------------------------------------------- |
| `captured_at`        | The instant the ad click landed               | **Dates the click.** Survives a return visit |
| `session_started_at` | When the converting visit began               | Bounds the click: at or before this          |
| `session_id`         | Same, derived from the session's own identity | Bounds the click: at or before this          |

Only `captured_at` can prove a click fell outside a platform's attribution window. The other two say the click happened *at or before* that moment, which is a weaker and still useful claim — but reporting it as a fact would produce confident, wrong findings.

<Warning>
  When no lead in a range carries a dated click, a report showing **zero** clicks that provably fell **before the range you are comparing** means *not measurable*, not *none*. Hiveku always reports the coverage — how many records carry a real click time — beside the finding, so a zero can be read correctly.
</Warning>

### When the stored campaign is not this lead's campaign

Because the store keeps first touch, a specific and easily-missed situation arises:

1. A visitor arrives in January on a newsletter link. The store keeps the newsletter's UTMs and stamps January.
2. In March the same browser clicks a Google ad and lands with a `gclid`. The store already has something, so **the ad's parameters are never stored**.
3. The visitor fills in a form. The `gclid` is recovered from the landing URL, so the lead is correctly identified as paid — but the stored timestamp still dates January's newsletter.

Dating that March click with January's timestamp would place the click two months before it happened, on a lead being compared against the platform's numbers. That is a false finding wearing the label a reader trusts most.

So the timestamp is only used to date a lead when the click ID came from the stored parameters themselves. Otherwise Hiveku falls back to the weaker, honest observation.

## Calls are attributed too, and differently

A phone call has no URL to read, so it is attributed by **which number rang**. Tracked numbers are assigned to visitors from a pool, and the call is matched to the visitor session that held that number **at the time the call came in** — not merely to whoever held it most recently.

That time-anchoring is the whole trick. Numbers are recycled between visitors, so matching without it would credit a call to whichever visitor happened to hold the number last.

When no session held the number at that moment, Hiveku tries one more thing before giving up: if the **same caller ID** has a prior attributed call on the same pool within 90 days, that earlier call's campaign is carried forward. A repeat caller dialling a number they saved weeks ago keeps their original attribution instead of becoming an anonymous call.

Only when both fail is the call marked explicitly as unmatched — recorded as unattributed rather than assigned to a plausible-looking campaign.

See [Phone tracking](/communications/phone-tracking) for setup.

## Why your numbers differ from the ad platform's

They are supposed to. A difference is not automatically an error on either side, and the audit tools report these categories explicitly rather than quietly netting them out:

<AccordionGroup>
  <Accordion title="The platform counts conversions that are not form fills">
    Calls, chats, purchases, store visits, and every other conversion action on the account. Check which conversion action the platform's number came from before comparing anything.
  </Accordion>

  <Accordion title="View-through conversions have no click at all">
    Someone saw the ad, did not click, and converted later. There is no click ID, so it can never appear on our side. If the platform's number includes view-through, it *should* be higher.
  </Accordion>

  <Accordion title="Cross-device and logged-in attribution">
    The platform can join a click on a phone to a conversion on a laptop, because it knows the person is signed in. Hiveku sees only what the submitting browser carried.
  </Accordion>

  <Accordion title="The two sides date conversions differently">
    Ad platforms credit a conversion to the day of the **click**. Hiveku dates a lead by when it was **submitted**. Windows that look identical disagree at both edges, and a lead that clicked in one month and converted in the next belongs to different periods on each side.
  </Accordion>

  <Accordion title="Auto-tagging off, or a redirect that strips the query string">
    Produces a real paid lead with no attribution at all. It lands in an unattributed bucket rather than being counted as organic.
  </Accordion>
</AccordionGroup>

## Reading a conversion audit

The form conversion audit answers "the platform says 40, the CRM shows 22" by sorting every submission in the range into named buckets that **sum to the total** — so the gap is accounted for rather than estimated:

| Bucket               | What it means                               |
| -------------------- | ------------------------------------------- |
| `counted`            | A real lead. This is the number to compare  |
| `deleted`            | Removed after the fact                      |
| `duplicate`          | The same physical submission captured twice |
| `spam`               | Filtered as junk                            |
| `archived`           | Filed away                                  |
| `workflow_failed`    | Captured, but downstream automation failed  |
| `no_attribution`     | A real lead carrying nothing to attribute   |
| `unpaid_attribution` | Attributed, but not to a paid click         |

The buckets sum to the rows the audit actually **scanned**. A large range is capped, and the response says so — reporting the in-scope total separately and flagging the run as truncated — because a capped run is a sample, not a total.

Deleted, duplicate and spam rows are **included and labelled** rather than hidden, because they are usually the answer. "40 versus 22, and here are 11 spam, 4 duplicates and 3 with no UTMs" resolves the argument. A bare number does not.

<Tip>
  Set the audit's timezone to the **ad account's** timezone. Platforms report in it, and comparing against UTC days will disagree at both ends of the range for reasons that have nothing to do with tracking.
</Tip>

## Consent, and what is deliberately not collected

Where **Hiveku's own consent banner** governs the page and the analytics category is not granted, the analytics embed does not initialise, so no durable first-touch attribution is captured or transmitted.

Hiveku also honours a **third-party consent banner** — OneTrust, Cookiebot, Usercentrics, CookieYes and similar — from analytics embed **v3.6** onward. It does this by reading Google Consent Mode, the signal every major banner emits because Google requires it: an explicit `analytics_storage` denial holds the embed, and an explicit `ad_storage` denial prevents the visitor from being identified.

<Warning>
  Only an **explicit denial** suppresses. If a banner is present but publishes no Consent Mode signal at all — or publishes one Hiveku cannot read — the embed initialises as it always has. This is deliberate: treating an unreadable signal as a refusal would stop tracking across every site the moment a vendor changed a field name, and a silent stop is a worse failure than a missed suppression. If your banner does not emit Consent Mode, gate the Hiveku tag yourself.
</Warning>

The advertising signal is recorded as three states, not two: granted, explicitly **denied**, and no signal at all. Only an explicit denial suppresses downstream use — a record with no signal behaves exactly as it always has. That distinction is what keeps every historical record from being treated as a refusal.

Separately, the lightweight form-capture path performs **no browser storage access whatsoever** — no reads, no writes, no cookies — and that is machine-checked by tests rather than left as an intention. Because it touches no storage it ships on every deployed site and is never consent-gated.

It captures less as a result, and it is worth being precise about what that means: it has no durable first-touch store, so it cannot recover a campaign from an earlier visit. But it **does** read the UTMs and click IDs present on the URL of the visit that submitted the form. A lead arriving on a `?gclid=` link still has that click ID recorded even when analytics consent was denied.

## What Hiveku does not do

Stated plainly, because knowing the edges is more useful than discovering them:

* **Multi-touch attribution models** — linear, time-decay, position-based, data-driven — are not offered. Campaign reporting works on first touch, last touch, or participation.
* **View-through conversions** cannot be captured. There is no click to record.
* **Cross-device identity resolution** is not performed. A click on one device and a conversion on another are two records unless something identifies the person.
* Attribution from the **fetch/XHR form path** requires embed v3.5 or later. Earlier cached copies of the embed submit those forms without campaign data.

## What's next

<CardGroup cols={2}>
  <Card title="Conversion Uploads" icon="arrow-up-from-line" href="/marketing/conversion-uploads">
    Send outcomes back to the ad platforms so bidding trains on real results
  </Card>

  <Card title="Track UTM Campaigns" icon="link" href="/how-tos/track-utm-campaigns">
    Tagging conventions and verifying capture on your own site
  </Card>

  <Card title="Phone Tracking" icon="phone" href="/communications/phone-tracking">
    Number pools, dynamic insertion, and how calls get attributed
  </Card>

  <Card title="Advertising Reports" icon="chart-line" href="/advertising/reports">
    Campaign ROI, client reports and journeys
  </Card>
</CardGroup>
