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:
| Column | Meaning |
|---|---|
first_touch_channel | Channel bucket — organic, direct, referral, social, email, paid, ai |
first_touch_utm_source | UTM source string from the URL, if present |
first_touch_utm_medium | UTM medium |
first_touch_utm_campaign | UTM campaign |
first_touch_utm_content | UTM content |
first_touch_utm_term | UTM term |
first_touch_referrer | Document referrer URL |
first_touch_landing_page | The first page URL they landed on |
first_touch_at | Timestamp 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:
- Click IDs (
gclid,fbclid,ttclid,msclkid) — always paid. utm_medium—cpc/ppc/paid/display/banner/cpm/native/rich_media→ paid;email→ email;social→ social;referral/affiliate→ referral;organic→ organic.utm_sourcematched against curated lists of paid-ad / social / email-ESP sources.- Referrer hostname — search engines → organic; known social platforms → social; webmail clients → email; otherwise → referral.
- In-app browser detection (Discord, Facebook, Instagram, etc.) — social.
- No referrer + no UTM → direct.
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:
utm_medium=email→ channel is email, regardless of referrer.- No referrer +
utm_sourcematching 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:
| Tab | Default attribution | What it shows |
|---|---|---|
| Channel | Toggle-aware | Visitors split into 9 buckets: Direct, Organic Search, Organic Social, Referral, Paid Search, Paid Social, Email, Affiliate, Display |
| Referrer | Toggle-aware | Top referring domains with favicons; in-app browsers (ChatGPT, Snapchat, etc.) classified via utm_source fallback |
| UTMs | Toggle-aware | Five sub-tabs: Source, Medium, Campaign, Content, Term. A (none) bucket counts visitors lacking that dimension |
| Keyword | Always last-touch | Pulled 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:
| Model | What it credits | When to use |
|---|---|---|
| Last-Touch | 100% to the last channel before converting | You want to know what closes deals |
| First-Touch | 100% to the channel that first brought the visitor in | You want to know what drives discovery |
| Linear | Split evenly across every channel in the journey | You want a balanced, neutral view |
| Position-Based | 40% first, 40% last, 20% split across the middle | You want to reward discovery AND closing |
| Time-Decay | More credit to channels closer to the conversion | You 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 mode | Cookieless mode | |
|---|---|---|
| What identifies a visitor | A random UUID in a first-party cookie, written by the tracker | A 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 device | One first-party cookie | Nothing durable — the in-page ID is a throwaway UUID that lives in memory for the page load |
| How long one identity survives | 30 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 see | Every recorded session inside the cookie window | Sessions inside the same UTC day |
| What First-Touch answers | What first brought this visitor in, ever | What first brought this visitor in that day |
| Last-Touch accuracy | Full | Full |
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.
Related
On this page