Skip to main content
12 min read

Attribution Models

Every visitor in Zenovay carries two attribution snapshots: their first-touch channel (how they first found you) and their last-touch channel (the channel of the session that converted, or the most recent session). Both are stored independently — first-touch is locked at the very first observation and never overwritten.

Why two snapshots

Marketing attribution is a question of credit. If a visitor first arrives via an organic Google search, comes back three weeks later via a paid LinkedIn ad, and converts on a direct visit:

  • Last-touch credits direct. (Often understates marketing.)
  • First-touch credits organic search. (Often overstates SEO.)
  • Linear / position-based / time-decay models split credit across every channel in the visitor's journey — selectable on the Revenue tab (see below).

Zenovay starts simple: both first-touch and last-touch are stored, and the dashboard's attribution surfaces let you toggle between them.

What first-touch captures

When a visitor is observed for the very first time, Zenovay populates these columns on their visitor row:

ColumnMeaning
first_touch_channelChannel bucket — organic, direct, referral, social, email, paid, ai
first_touch_utm_sourceUTM source string from the URL, if present
first_touch_utm_mediumUTM medium
first_touch_utm_campaignUTM campaign
first_touch_utm_contentUTM content
first_touch_utm_termUTM term
first_touch_referrerDocument referrer URL
first_touch_landing_pageThe first page URL they landed on
first_touch_atTimestamp of the first observation

These are never overwritten on subsequent sessions. The corresponding non-prefixed columns (channel, utm_*, referrer, landing_page) are updated each session and represent last-touch.

Visitors observed before 2026-04-30 have NULL first-touch values. The history was not back-filled — see your retention policy for what historical data is available.

Channel classification

Channels are classified in this priority order:

  1. Click IDs (gclid, fbclid, ttclid, msclkid) — always paid.
  2. utm_mediumcpc/ppc/paid/display/banner/cpm/native/rich_mediapaid; emailemail; socialsocial; referral/affiliatereferral; organicorganic.
  3. utm_source matched against curated lists of paid-ad / social / email-ESP sources.
  4. Referrer hostname — search engines → organic; known social platforms → social; webmail clients → email; otherwise → referral.
  5. In-app browser detection (Discord, Facebook, Instagram, etc.) — social.
  6. No referrer + no UTMdirect.

A visitor flagged as bot-like by Zenovay's AI heuristic has their channel overridden to ai regardless of UTM.

Email traffic — the empty-referrer fix

Email links typically have no referrer header. Without UTM parameters, this used to misclassify email traffic as direct.

Zenovay catches this in two ways:

  1. utm_medium=email → channel is email, regardless of referrer.
  2. No referrer + utm_source matching a known ESP (Mailchimp, Klaviyo, HubSpot, ActiveCampaign, Brevo, ConvertKit, MailerLite, Beehiiv, Customer.io, ActiveCampaign, GetResponse, Omnisend, Substack, Drip, Intercom, Postscript, Attentive, Iterable, Braze, Resend, Loops, Marketo, Pardot, and others) → channel is email.

The full ESP list lives in api-zenovay/src/constants/domains.ts (EMAIL_UTM_SOURCES). To make sure your email campaigns classify correctly:

<!-- Recommended UTM convention for all email links -->
<a href="https://your-site.com/?utm_source=mailchimp&utm_medium=email&utm_campaign=launch">
  Read more
</a>

Either utm_medium=email or a recognized utm_source is enough — you don't need both.

Reading attribution in the dashboard

The Sources tab and visitor detail panel let you switch between first-touch and last-touch directly in the UI — you no longer need to query the database to compare the two.

Sources tab

The Sources card on the analytics dashboard has a First touch | Last touch toggle in its header. The selection persists in the URL (?attribution=first or ?attribution=last), so refresh and back/forward navigation preserve your choice. Default is last-touch (matches the prior behavior).

The card has four top-level views:

TabDefault attributionWhat it shows
ChannelToggle-awareVisitors split into 9 buckets: Direct, Organic Search, Organic Social, Referral, Paid Search, Paid Social, Email, Affiliate, Display
ReferrerToggle-awareTop referring domains with favicons; in-app browsers (ChatGPT, Snapchat, etc.) classified via utm_source fallback
UTMsToggle-awareFive sub-tabs: Source, Medium, Campaign, Content, Term. A (none) bucket counts visitors lacking that dimension
KeywordAlways last-touchPulled from your Google Search Console connection; first-touch toggle does not apply

When you flip the toggle to First touch, the Channel / Referrer / UTM views all switch to the visitor's first observed values. Imported analytics (Plausible CSV imports) are last-touch only — those rows are skipped on first-touch view to keep the model honest.

Public dashboards always show last-touch attribution. The toggle is owner-only in V1; we'll surface a server-side default per-share in a follow-up.

Visitor detail card

When you click into a visitor's profile, the sidebar shows a compact Attribution panel: first-touch (channel + utm_source + landing page + first-seen date) on the left, last-touch (channel + utm_source + referrer + landing page) on the right. The panel hides itself for visitors observed pre-FND-B (≈2% of historical) so you don't see a row of em-dashes.

Revenue tab

The Revenue tab's Attribution card has a five-model selector — Last-Touch (default), First-Touch, Linear, Position-Based, and Time-Decay. The choice persists in the URL (?model=), so a refresh and back/forward navigation keep the model you picked. The default is last-touch.

The five attribution models

Each model answers the question "which channel gets credit for this conversion?" a different way:

ModelWhat it creditsWhen to use
Last-Touch100% to the last channel before convertingYou want to know what closes deals
First-Touch100% to the channel that first brought the visitor inYou want to know what drives discovery
LinearSplit evenly across every channel in the journeyYou want a balanced, neutral view
Position-Based40% first, 40% last, 20% split across the middleYou want to reward discovery AND closing
Time-DecayMore credit to channels closer to the conversionYou have shorter sales cycles

Time-Decay uses a 7-day half-life: a touch 7 days before the conversion gets half the weight of a touch at conversion time, and the weighting keeps halving for every additional 7 days.

A note on data history

If most conversions in a period came from a single session, the multi-touch models (Linear, Position-Based, Time-Decay) will look very similar to Last-Touch — there is only one channel to spread credit across. This is expected, not a bug.

In first-party cookie mode it resolves on its own as more multi-session journeys accumulate. In cookieless mode it does not: the journey the models can reconstruct is capped at a single day by design, so multi-touch models stay close to Last-Touch no matter how long you collect. See Cookieless mode and attribution depth below.

How each model reads your data

The five models do not all draw from the same source:

  • Last-Touch and First-Touch use a single snapshot. Last-Touch credits the channel recorded on the converting session; First-Touch credits the channel from the visitor's very first recorded session. No journey reconstruction is needed.
  • Linear, Position-Based, and Time-Decay reconstruct the visitor's recorded multi-session journey, pulling every joinable visit before the conversion and distributing credit across the sessions found. How far back that journey reaches is not unlimited — it is bounded by your domain's visitor storage mode, which is covered in Cookieless mode and attribution depth below.

One consequence of this difference: on identical underlying data, the models can agree completely or diverge noticeably, depending on how many distinct sessions and channels a visitor passed through before converting. A visitor with a single-session journey gives all models the same answer. A visitor with five sessions across three channels will produce measurably different outputs from each model.

Why your models can look almost identical

When you switch between attribution models and the channel breakdown barely changes, the data is behaving correctly — this is not a display issue.

If most of your conversions came from visitors who had only one session (or who consistently used only one channel before converting), every model mathematically awards that channel 100% of the credit. Last-Touch, First-Touch, Linear, Position-Based, and Time-Decay all reach the same answer because there is nothing to distribute differently. As your audience grows and more visitors touch multiple channels across multiple sessions before converting, the models will diverge and become more informative to compare.

A few additional things to keep in mind as you compare models:

  • AI traffic is always credited to the dedicated ai channel under every attribution model. AI detection takes precedence over the touch path, so the AI row stays constant when you switch models.
  • A conversion with no monetary value still counts toward conversion attribution. Revenue-weighted comparisons require a monetary value to be attached to the goal — without it, those conversions appear in counts but contribute nothing to the revenue totals shown by each model.

Empty UTM dimensions

Most visitors don't carry every UTM parameter. In real Zenovay data today, utm_term is ≈100% null and utm_content ≈99.99% null. The Sources tab shows an empty-state with a copyable hint (?utm_term=...) for those views rather than rendering a single 100% bar of (none).

Cookieless mode and attribution depth

Every domain runs in one of two visitor storage modes. You pick it when you add a domain (Visitor storage: Cookieless or First-party cookie) and can change it later under Settings → Advanced → Cookieless Mode. This is not only a privacy setting — it decides how much journey the multi-touch models have to work with.

First-party cookie modeCookieless mode
What identifies a visitorA random UUID in a first-party cookie, written by the trackerA pseudonymous ID derived on our server from the IP subnet, the user agent, and a salt that rotates every day
Stored on the visitor's deviceOne first-party cookieNothing durable — the in-page ID is a throwaway UUID that lives in memory for the page load
How long one identity survives30 days by default, rolling: rewritten on every visit, so only a genuine 30-day absence forgets a visitor. Configurable per domain.Until 00:00 UTC. A new pseudonymous ID is derived the next day.
Journey the multi-touch models can seeEvery recorded session inside the cookie windowSessions inside the same UTC day
What First-Touch answersWhat first brought this visitor in, everWhat first brought this visitor in that day
Last-Touch accuracyFullFull

What this means model by model

Last-Touch is unaffected by the storage mode. It reads the channel recorded on the converting session itself, so it never needs a journey at all. If you run cookieless and want one number that carries no caveats, this is it.

First-Touch keeps working, but the question it answers narrows. The first_touch_* columns are written when an identity is first observed, and in cookieless mode a new identity is derived each day — so a visitor who discovered you through organic search on Monday and converts on Thursday is credited with Thursday's entry channel, not Monday's.

Linear, Position-Based, and Time-Decay rebuild the journey by joining recorded sessions on the visitor ID. In cookieless mode that join cannot cross the daily rotation, so the reconstructed path is same-day. When a converting visitor has no joinable session history at all, all three fall back to Last-Touch on the conversion's own UTM parameters and referrer, and the Attribution card labels the period as limited history rather than showing you a split that isn't real.

Time-Decay deserves one extra note. Its 7-day half-life assumes touches spread across days; within a single day every touch weighs between roughly 0.91 and 1. In cookieless mode Time-Decay therefore lands close to an even split.

Switching modes is not symmetric

Turning cookies on later does not backfill. Cross-day identity can only be built forward from the moment cookies start, so returning-visitor links for the cookieless period are gone permanently. The other direction — cookies to cookieless — loses nothing that was already recorded.

Two things cookieless identity cannot do

  • Two visitors can merge. Colleagues behind the same office NAT running the same browser version share a subnet and a user agent, so for that day they resolve to a single visitor.
  • One visitor can split. Someone moving from mobile data to Wi-Fi changes subnet mid-day and is counted as two visitors, dividing their journey between them.

Both follow from deriving identity from network and browser characteristics instead of storing something, and both are why the ceiling is described as same-day rather than same-day guaranteed.

Identify calls and payment webhooks

Calling zenovay('identify', ...), or receiving a signed payment webhook, attaches a known person — email, customer ID — to the visitor ID that is current at that moment. It does not reconstruct the anonymous days that came before, because those IDs cannot be re-derived. Identification tells you who converted; it does not extend the anonymous journey the multi-touch models read.

Choosing a mode

If cross-day multi-touch attribution matters to your reporting, first-party cookie mode is a requirement rather than a preference. If you want a tracker that puts nothing on the visitor's device, cookieless delivers exactly that, and the honest cost is attribution depth.

Storing nothing on the device addresses the storage question under ePrivacy Art. 5(3). Having a lawful basis for the processing itself under GDPR Art. 6 is a separate question that cookieless mode does not answer on its own. See Privacy-first.

Was this page helpful?