learn · tracking plan
The id chain
Joining a payment back to the rule that found the person.
In Tracking plan
Joining a payment back to the rule that found the person. Every id, who mints it, where it travels, and the one link most setups are missing.
rule 1 — Mint once · One service creates each id. Everything else carries it.
rule 2 — Cross in the link · The URL is the only bridge from a message to the site.
rule 3 — Count the orphans · Conversions with no chain are measured, not ignored.
what it is, in industry terms
One chain of keys from the rule that found a person to the payment.
Industry names: data lineage, join keys, touch attribution chain. Each id is minted once, at the step that creates the thing, and carried forward on every later event. Read the chain right to left to attribute a payment; left to right to see what a detector produced.
Minted — primary key creation · The one place an id is created. Never re-created downstream.
Carried — foreign key · Every later event about the same thing includes it.
Alias — identity merge · When two person records turn out to be one person, both ids stay valid and point to a survivor.
Orphan — unattributed conversion · A sign-up or payment with no chain back to a touch.
how it works
The chain, left to right.
- detector — detector_id + v
- run — run_id
- raw item — raw_item_id
- match — match_id
- signal — signal_id
- lead — lead_id
- move — move_id
- touch — touch_id
- click · visit — link_id
- sign-up — first + last touch
- payment — Stripe customer
- detector — detector_id + v
- run — run_id
- raw item — raw_item_id
- match — match_id
- signal — signal_id
- lead — lead_id
- move — move_id
- touch — touch_id
- click · visit — link_id
- sign-up — first + last touch
- payment — Stripe customer
Without touch_id the chain breaks between what we sent and what happened.
Mint at creation — The service that creates a thing creates its id, and nothing else does.
Carry on every event — An event about a touch carries touch_id and everything upstream it knows: lead_id, campaign_id, move_id.
Cross the boundary in the link — The only way a click on X, Reddit or email reaches the site is the URL. Put the touch_id in the short link, then in the visit, then on the sign-up.
Ask when links fail — A DM read but not clicked leaves no trail. "How did you hear about us?" on the sign-up form catches it.
Keep aliases — When identity resolution merges two people, keep both person_ids pointing at the survivor so old events still join.
the object
The id registry: one row per id type.
id registry entry
| Field | Type | What it holds |
|---|---|---|
id | string | Name of the key, e.g. touch_id. |
minted_by | service | The only code allowed to create it. |
minted_when | event | The event that creates it. |
format | string | uuid, prefixed slug, short code. |
carried_on | event types | Every event that must include it. |
joins_to | ids | Its neighbors in the chain. |
required | boolean | Whether events of carried_on types are rejected without it. |
{
"id": "touch_id",
"minted_by": "sequence send step",
"minted_when": "touch.sent",
"format": "short url-safe code, 8 chars",
"carried_on": [
"touch.sent",
"link.clicked",
"visit.started",
"reply.received",
"signup.created"
],
"joins_to": [
"move_id",
"lead_id",
"link_id",
"account_id"
],
"required": true
}the trace query, as data
| Field | Type | What it holds |
|---|---|---|
conversion | event | The sign-up or payment being explained. |
chain | list | Each hop with the id that joined it. |
broken_at | string or null | The first hop with no id. |
{
"chain": [
[
"visit",
"touch_id=k3x9q2"
],
[
"touch",
"lead_id=lead_88"
],
[
"lead",
"signal_id=sig_402"
],
[
"signal",
"detector=det.pain_post.reddit v4"
]
],
"broken_at": null
}the catalog
Every id in the chain.
listen · who is ready — 8
trigger_id
listen
trigger_id
Minted by the trigger catalog, when a trigger is defined.
- format
- trig.<slug>
- joins to
- detectors, signals
detector_id + version
listen
detector_id + version
Minted by the detector catalog, when a detector is created; version bumps on each rule change.
- format
- det.<trigger>.<source> + v
- joins to
- runs, matches, signals
run_id
listen
run_id
Minted by each scheduled fetch, start of a run.
- format
- uuid
- joins to
- raw items, run stats
raw_item_id
listen
raw_item_id
Minted by the source, when an item is stored.
- format
- source + external id
- joins to
- matches, signals
match_id
listen
match_id
Minted by the matcher, when a rule fires.
- format
- uuid
- joins to
- judge verdict, discard
signal_id
listen
signal_id
Minted by the judge, when a match passes.
- format
- uuid
- joins to
- lead, routing queue
person_id
listen
person_id
Minted by identity resolution, first record of a person.
- format
- uuid
- joins to
- all their handles and leads
lead_id
listen
lead_id
Minted by the lead store, when a person qualifies.
- format
- uuid
- joins to
- scores, campaigns, moves
act · what we did — 5
campaign_id + arm
act
campaign_id + arm
Minted by the campaign, at assignment.
- format
- uuid + arm name
- joins to
- moves, touches, readouts
move_id
act
move_id
Minted by the next-best-action engine, when a move is chosen.
- format
- uuid
- joins to
- candidates, propensity, draft
draft_id
act
draft_id
Minted by the drafter, when a draft is generated.
- format
- uuid
- joins to
- decision, post
touch_id
act
touch_id
Minted by the send, when a message goes out.
- format
- short, url-safe
- joins to
- reply, click, visit, sign-up
post / tweet id
act
post / tweet id
Minted by the channel, when a post publishes.
- format
- platform id
- joins to
- snapshots, replies
outcome · what happened — 4
link_id
outcome
link_id
Minted by the short-link service, when a link is minted.
- format
- short code
- joins to
- clicks, visits
visit_id
outcome
visit_id
Minted by the site, first page view.
- format
- uuid
- joins to
- sign-up
account_id
outcome
account_id
Minted by sign-up, account or waitlist entry.
- format
- uuid
- joins to
- activation, payment
Stripe customer
outcome
Stripe customer
Minted by Stripe, checkout completes.
- format
- cus_…
- joins to
- invoices, cancellations
failure modes
Where chains break.
| Failure | Symptom | Catch | Fix |
|---|---|---|---|
| Link params stripped | clicks with no touch | share of visits from short links with no touch_id | encode the touch in the short code itself, not a query param |
| Copy-pasted URL | visits from the bare domain | referrer vs short-link match rate | self-reported source on sign-up |
| Two ids for one person | one lead gets two cadences | duplicate handles across person records | identity resolution with aliases |
| Merge rewrites history | old events stop joining | join rate drops after a merge | alias table; never rewrite person_id on old events |
| Id minted in two places | collisions or gaps | uniqueness checks per id | one minting service per id |
| Unread DM | conversion with no click | orphan rate | accept it; count self-reported answers |
data to capture
What this component records, and what that data trains.
record
- every id on every event it applies to
- alias rows for every merge and split
- orphan conversions, kept and counted
label
- the full chain behind each conversion is the ground truth for attribution
trains
- attribution models
- per-detector and per-playbook yield
- reply propensity joins (lead → touch → reply)
test
- weekly orphan rate
- a trace test: synthetic lead through to a test payment