Documentation
Identity resolution
Somebody reads your pricing page on a phone, leaves an address on a laptop two days later, signs up in your app, and is billed under an id that looks like nothing at all. That is one person, and in most stacks it is four rows in four tools. This is how Kicklace keeps it one record, what it will never join on its own, and what happens when you merge two by hand.
A person meets your product several times before you know their name — a phone, then a laptop, then your app, then a billing id that looks like nothing at all. In most stacks that is four rows in four tools, and joining them up is a project. In Kicklace it is one record with one timeline, and how it stays one record is the subject of this page.
The record, and the keys that find it
Everything Kicklace knows about somebody hangs off one record. Beside it sit its identities — the keys other systems know that person by:
| Kind | What it is | Who writes it |
|---|---|---|
email |
an address, lowercased | a form, an import, your own server, a door |
anon |
the random id the site tag keeps in localStorage |
the tag |
account |
your own id for their account in your product | your server, as account_id |
stripe_customer |
cus_… |
Stripe |
clerk_user, kinde_user |
their user id at your auth provider | Clerk or Kinde |
github_user |
a GitHub handle | GitHub |
Two more kinds exist in the model and are not in the table because no other system writes them for
a person: domain, which an organization carries, and install, an id for one installation of a
downloadable app.
Any of them finds the person. Post an event with an email, an anon_id, an account_id or all
three and it lands on the same record; a Stripe subscription that carries only a customer id lands
there too, once one earlier event has arrived carrying the id and an address together.
An identity that is on a record stays on it. Kicklace never guesses a person from a browser fingerprint, an IP address or a name, and it has none of the machinery that would let it.
The four rules
1. The address is the key that crosses devices. Two browsers, one laptop and one phone, with the same address typed into both, are one person — because the address is the same and nothing else has to be.
2. A known address plus a new browser attaches that browser. Somebody who already exists fills a
form on a machine Kicklace has never seen: the new anon id is added to the record they already
have. Their history is not split.
3. A known address arriving on a browser that belongs to an anonymous person merges the two. The browser had already been reading pages for a fortnight under no name at all. Now it has one. The anonymous record is folded into the identified one — its identities, its timeline, its subscriptions, its links — and the earliest first touch wins. What that visitor was first recorded arriving from is what the person is still attributed to a year later.
4. Two people who both have an address are never merged automatically. A shared laptop is a real thing. If the browser id already belongs to somebody with a different address, the new address starts its own record and the browser id stays where it was. Merging two named people is a decision, and a person makes it — see below.
First touch is kept for ever
Every record carries a first touch and a last touch: the source, medium, campaign, ad click id and path of an arrival. First touch only ever moves earlier. A later visit can never overwrite it, and neither can a merge: where two records are folded together the earlier first touch survives and the later last touch survives.
That is what makes the attribution question answerable. "Which campaign brought us the people who paid this quarter" reads first touch, and first touch is still the ad they clicked in March.
Merging by hand
Sometimes there really are two records for one person — two addresses, a typo, a spreadsheet import — and no rule should join them on its own. Merge… on a record page (owners and admins) finds another record of the same kind, shows a preview of what will happen, and asks which one survives.
When it runs:
- the survivor's own values stand; its blanks are filled from the other one;
- identities, timeline rows, links, subscriptions, tasks, waiting automation runs and sent emails move across;
- the earliest first touch and the latest last touch win;
- the further lifecycle stage wins, so merging never moves anybody backwards;
- the record that was folded in is marked, and from then on its page redirects to the survivor, so no link anybody has ever saved breaks;
- a note on the survivor's timeline says what moved and who did it.
Two records of different kinds cannot be merged, and neither can a record that has already been folded into another one. Contacts → Duplicates finds likely pairs — the same name, the same phone, the same last name at the same email domain, the same organization domain — and opens the merge dialog with both sides filled in.
A merge is not one database transaction, on purpose: every step is safe to run twice and the record is marked last, so a merge interrupted halfway is finished simply by running it again.
What a person can ask you for
Everything you hold. A record's export is a JSON file with all of it: the fields, every identity, every subscription with the words that person agreed to and when, the whole timeline, every email sent to them, their links to other records, and the records that were merged into them. Any member can take it, from the record's own menu.
Deletion. Deleting a record deletes the record, every row merged into it, and everything hanging off them — identities, timeline, subscriptions and their consent records, tasks, sends, links. The people who run the workspace are told that a record was deleted; the notice never names the person, because naming somebody in a notice about erasing them would be absurd.
Visitors nobody came back as
Most anonymous visitors never leave an address. Keeping them for ever is a cost with no purpose, so a workspace can set a retention period under Settings → General → Privacy, and a background job, every hour, deletes anonymous people who have not been seen since.
"Anonymous" is narrow, deliberately. Somebody is only swept if they have no email field, no email
identity, no account, no Stripe customer, no Clerk or Kinde user, no GitHub handle, and were not
merged into anybody. Anybody another system has named is a person, and stays. Setting the period to
0 keeps everything.
Your own senders can go further: an event posted to POST /api/v1/events may carry an expires_at,
which is your promise about how long that one row may be kept, and Kicklace honours it — the row is
deleted on that day, and the person, their stage, their first touch, their subscriptions and the
consent behind them all stay. Events has the rule.