learn · funnel and metrics
Attribution
First touch, last touch and the touch chain.
In Funnel and metrics
Tracing each waitlist join and payment back to the touches before it: first touch, last touch, self-reported, and the one model that measures cause, the holdout.
rule 1 — Keep every model · Last touch, first touch and self-report, side by side.
rule 2 — Count the orphans · Unattributed joins are a number, not a shrug.
rule 3 — Cause needs a holdout · Otherwise it only "came after".
what it is, in industry terms
Which message, play and detector produced this sign-up?
Industry names: multi-touch attribution, first touch / last touch, self-reported attribution, incrementality. Attribution assigns credit for a conversion to the touches before it. Only a holdout says what a campaign caused; every other model says what came before.
Touch — touchpoint · Any message, post or page the person saw that we can identify.
Conversion — conversion event · A waitlist join, sign-up, activation or payment.
Lookback window — attribution window · How far back a touch can still get credit. 30 days by default here.
Last touch — last-click · All credit to the final touch before conversion.
First touch — first-click · All credit to the touch that opened the door.
Self-reported — HDYHAU · "How did you hear about us?" in the person's words.
Incrementality — lift · What happened because of the touch, measured against a holdout.
how it works
From a conversion back to the touches that led to it.
- Conversion — join, sign-up, payment
- Find the visit — touch_id in the link
- Collect touches — same person, 30 days
- Apply models — first, last, self-report
- Write credit rows — one per model
- Report — weekly and monthly
- Conversion — join, sign-up, payment
- Find the visit — touch_id in the link
- Collect touches — same person, 30 days
- Apply models — first, last, self-report
- Write credit rows — one per model
- Report — weekly and monthly
Start from the conversion — Each sign-up or payment event is a conversion to explain. Stripe gives payments; the ladder gives waitlist joins.
Find the person's touches — Through the visit's touch_id, then the lead's other touches inside the lookback window.
Credit under each model — Write a credit row per model: last touch, first touch, self-reported. Never pick one and throw the rest away.
Roll up — Sum credits by detector, playbook, campaign arm, channel and draft variant.
Check against holdouts — Where a campaign had a holdout, compare: credit claimed vs lift measured.
the object
The attribution record.
conversion attribution
| Field | Type | What it holds |
|---|---|---|
conversion_id | event id | The sign-up or payment. |
account_id | uuid | |
conversion_type | waitlist | signup | activation | payment | |
touches | list | Every touch in the window, in order, with touch_id, channel, playbook, detector, arm. |
first_touch | touch_id | |
last_touch | touch_id | |
self_reported | text | Verbatim answer, if given. |
credits | object | Credit per model per touch. |
lookback_days | integer | 30 by default. |
orphan | boolean | No touch found. |
{
"conversion_type": "waitlist",
"touches": [
{
"touch_id": "k3x9q2",
"channel": "reddit",
"playbook": "listen-and-reply",
"detector": "det.pain_post.reddit",
"arm": "treatment"
}
],
"first_touch": "k3x9q2",
"last_touch": "k3x9q2",
"self_reported": "saw your reply on r/SaaS",
"credits": {
"last_touch": {
"k3x9q2": 1
},
"first_touch": {
"k3x9q2": 1
}
},
"lookback_days": 30,
"orphan": false
}the catalog
Every kind of touch, and how traceable it is.
fully traceable — 4
trackable: high once touch_id exists
Public reply with link in a reply
On X, Threads, Reddit.
- carries
- short link carrying touch_id
- measure
- clicks and visits; replies by thread
- tracking
- high once touch_id exists
trackable: high
DM with a short link
On X, Instagram.
- carries
- short link carrying touch_id
- measure
- click, visit, sign-up
- tracking
- high
trackable: high
On email.
- carries
- tracked link + touch_id
- measure
- click, visit, sign-up
- tracking
- high
trackable: high
Comment-a-keyword reply
On X, Instagram.
- carries
- auto-DM with short link
- measure
- keyword comments, clicks
- tracking
- high
partly traceable — 4
trackable: medium
Own post, link in reply
On X, Threads.
- carries
- short link per post
- measure
- clicks per post; profile clicks
- tracking
- medium
trackable: medium
Waitlist page from a bio link
On all.
- carries
- bio short link
- measure
- visits from the bio link
- tracking
- medium: no message-level credit
trackable: medium
Comparison or alternatives page
On search, AI answers.
- carries
- UTM on internal links; referrer
- measure
- landing page and referrer
- tracking
- medium
trackable: medium
Directory or launch site
On Product Hunt, Uneed, BetaList.
- carries
- referrer + UTM
- measure
- visits and sign-ups by referrer
- tracking
- medium
mostly self-reported — 4
trackable: low
DM with no link
On X, Instagram.
- carries
- nothing travels
- measure
- only the reply, and self-reported source
- tracking
- low
trackable: low
Own post, no link
On X, Threads, TikTok, Reels.
- carries
- none (view-through)
- measure
- profile clicks, follows, self-reported
- tracking
- low
trackable: low
Named by an AI assistant
On ChatGPT, Claude, Perplexity.
- carries
- referrer when linked; often none
- measure
- self-reported; prompt panel share
- tracking
- low
trackable: low
Word of mouth
On anywhere.
- carries
- nothing
- measure
- self-reported only
- tracking
- low
illustrative example
Reading attribution before launch.
Each week the report shows joins by last touch, and once a month by first touch, with the self-reported answers listed beside both.
Two readings change decisions. A channel with lots of last-touch credit and no first-touch credit closes but doesn't find people, so it needs a detector feeding it. A channel with the reverse finds people but doesn't close, so its follow-up sequence is the problem.
Without a touch id on every message, only post-level and short-link credit is possible: which post or link a visit came from, not which lead or draft.
one rule — Never say a campaign "caused" joins without a holdout. Say "came after".
failure modes
How attribution misleads.
| Failure | Symptom | Catch | Fix |
|---|---|---|---|
| Dark social | joins with no referrer | orphan share per week | self-reported question on every sign-up |
| Last touch overcredits closers | one channel looks like it does everything | compare first vs last touch | read both |
| Overlapping windows | one join credited three times | credit sums above 1 per conversion | normalize per model |
| Correlation read as cause | a campaign "worked" in a good week | no holdout on file | holdout lift before claiming cause |
| Self-report noise | "twitter" for everything | answer category counts | free text, categorized later; keep verbatim |
data to capture
What this component records, and what that data trains.
record
- a credit row per model per conversion
- the self-reported answer, verbatim
- orphans, counted
label
- the conversion itself is the label; the touches are the features
trains
- per-detector and per-playbook yield
- channel mix decisions
- uplift models, when holdouts exist
test
- orphan rate
- claimed credit vs holdout lift on every campaign that had one