Where a visitor came from, worked out rather than declared.
The premise: a marketer should not have to tag anything. Ad platforms already
stamp a click id on every click they sell, browsers already send a referrer,
and between the two almost every visit can be attributed without a single
utm_source. UTM parameters are still read when present — they are an
explicit statement of intent and beat inference — but they are an override,
not the mechanism.
Resolution order
- A paid click id (
fbclid,gclid,ttclid, …). Highest confidence: the platform stamped it, so somebody was charged for this visit. SeePixelex.Attribution.ClickIds. - Explicit campaign parameters —
utm_*, or Plausible's shortref. Deliberately below click ids: a staleutm_sourcecopied into a link is common, a forgedgclidis not. - The referrer, classified by
ref_inspectorinto search / social / email / paid / referral, with the search term when the engine leaks it. - Direct — nothing to go on.
A referrer from the site's own domain is not a referral; it is someone
clicking around. Pass :hostname so those resolve to :internal and leave
the visitor's real first touch intact.
First touch and last touch
Both are kept. Last touch answers "what closed this", first touch answers
"what found them", and a library that stores only one has picked a side of an
argument that is not its to settle. merge/2 is the rule: first touch is
written once and never overwritten, last touch is overwritten by any new
non-direct touch — a direct visit does not erase the campaign that earned it.
Degrading without ref_inspector
ref_inspector is an optional dependency, so referrer classification falls
back to the referring hostname as the source with medium "referral". Click
ids, UTM parameters, and self-referral detection all still work; only search
vs. social vs. email gets coarser. Pixelex.Attribution.classifier/0 reports
which mode is active.
Summary
Functions
:ref_inspector when the classifier is available, :hostname when degraded.
Is this touch attributable to nothing at all?
Fold a new touch into a stored %{"first" => …, "last" => …} map.
Struct to a JSON-safe map, dropping empty fields.
Resolve one touch from a landing URL and a referrer.
Types
Functions
@spec classifier() :: :ref_inspector | :hostname
:ref_inspector when the classifier is available, :hostname when degraded.
Is this touch attributable to nothing at all?
Fold a new touch into a stored %{"first" => …, "last" => …} map.
First touch is immutable. Last touch is replaced by any touch that is not direct — a visitor returning by typing the URL should not wipe out the ad that brought them yesterday.
Struct to a JSON-safe map, dropping empty fields.
Resolve one touch from a landing URL and a referrer.
Options
:hostname— the site's own host, so self-referrals are recognised.
Examples
iex> t = Pixelex.Attribution.touch("https://shop.test/x?fbclid=IwAR9", nil)
iex> {t.network, t.medium, t.click_id}
{"facebook", "paid_social", "IwAR9"}
iex> t = Pixelex.Attribution.touch("https://shop.test/x", nil)
iex> {t.source, t.medium}
{"direct", "none"}