Link tracking · spec — fifth pass · 26 Aug 2026
Every link we publish is a short URL through zaneteknits.com/go, and it says everything: where it was posted, which push it belongs to, which destination it goes to. Any sentence made of registered words just works — nothing is minted per combination. Because the click lands on our site first, we enrich it from our own database before forwarding. PostHog holds the answers; we build the workflow, not another dashboard.
Replaces the July spec. Incorporates the Analytics solution P1 + P2 review with Zanete (25–26 Aug). First real outing: the Austerskorks partner links, live by Tuesday 1 September.
The problem
“The analytics we have right now… all it does is confirm what you already know.”
Asim — strategy conversation, 24 Aug“A lot of traffic is coming through and it comes up as direct.”
Asim, on TikTok“Of the people that bought — how many came from each of those platforms?”
Zanete, on a launch“I can share with my partners: look, we gave you 500 people in traffic yesterday. Tell me how many bought.”
Zanete, on affiliatesBehind those four quotes, four facts: short links carry no context (nobody knows what any short.io link was for, so nobody dares touch them); platforms hide origins (in-app browsers strip referrers, so TikTok files under “direct”); campaigns exist only in Zanete's head (there is no record anywhere of what a launch was, when it ran, or what it pointed at); and clicks leaving our site are invisible (a click to mYak or Ravelry counts nowhere).
The model
Where the link was posted — ig-mc (Instagram ManyChat), ig-story, fb-mc, nl, zk for our own site, myak for a partner's. Always the first word.
The push, or simply the grouping — a launch or sale with dates, or a standing campaign marked evergreen (a setting, not a special campaign). Campaigns don't own destinations; they group shares.
A named place a link can go: a short hyphenated slug (pat-himma, myak-himma) plus the real URL it points to. The slug is frozen once shared; the URL behind it can be corrected any time.
And link means the whole thing — the full short URL that gets pasted, printed and shared. Each destination can have many links pointing at it; that's the point.
Exactly three words, always in this order. The slash separates concepts and nothing else — inside a word it's hyphens (pat-himma, never pat/himma), so a link always parses the same way. The connection between the three is made by the link itself, at the moment of the click — not by anything stored.
Register each word once; any sentence made of registered words works. Promote the same destination in a new campaign tomorrow — go/nl/xmas-sale/myak-himma — and that's zero new records. New source? Zero records. Fifth campaign for an old destination? Zero records.
The words are registered because one typo (hima-launch) would otherwise split a campaign's numbers silently and forever. Registration is free in practice: a campaign gets created in the admin anyway for its dates and description.
The only person who mistypes a link is us — and it may be too late to fix what shipped (the newsletter is already sent). So a link with an unregistered source or campaign still forwards: the unknown word is auto-created as a placeholder, flagged in the admin, and Zanete can later merge it into the real one (it was actually ig) — from then on the reports read right, and only the pre-fix clicks lose a little detail. Campaigns can carry aliases for exactly this.
The destination is the one word that must be real — it holds the URL, and we can't forward to a destination we don't have. An unknown destination gets our clear “this link isn't set up” page.
pat-himma is about Himma no matter which campaign shares it. A destination's own relation always wins.A destination's slug and a campaign's name are frozen once emitted — they're printed in PDFs and baked into historical events. Everything else is alive: the URL behind a destination can be repointed, relates corrected, campaigns keep growing. A destination can even be created as a placeholder before its page exists — the Ravelry listing that isn't published yet — and repointed the day it's real.
What a click records
The secret sauce in one sentence: the link lands on our site, and our site has our database — so every click is enriched with everything we know before the visitor is forwarded.
Someone taps zaneteknits.com/go/ig-mc/himma-launch/myak-himma — the link ManyChat just sent them in an Instagram DM. It will land them on mYak's Tibetan Cloud yarn page, by way of our site.
The three words are read against the registers, and one lookup fills in the whole story:
ig-mc word
campaignhimma-launch · 2–16 Sep, launch
destinationmyak-himma · → mYak's Tibetan Cloud page
productHimma · campaign default
partnermYak · set on the destination
personasim@example.com · signed in here before, so we know
Never signed in? The click is still counted — anonymously, and no person is ever invented for it.
Forwarded to myak.com/collections/tibetan-cloud — exactly as stored. Off-site, nothing of ours rides along and their affiliate codes are untouched. When the destination is one of our own pages, the campaign and source tags do ride along, so the visit — and anything bought in it — attributes too.
All of this happens in milliseconds, and the counting happens after the visitor is already on their way — a slow database can never slow a click.
Problem → solution 1
Today a campaign is a plan in Zanete's head and a scatter of untraceable links. Instead: a campaign is a record she creates in one minute, with dates (or evergreen), a description, and defaults — and a page that features the destinations the push uses.
Adding destinations is a paste: drop in one URL — or a whole list — and existing destinations are recognised, new ones created (as placeholders if the page isn't published yet), each with a suggested slug. Featuring costs nothing; what attributes a click is the link itself, never the list. A campaign starts with one destination and grows for as long as it runs.
Three screens, and the only typing is a name and pasted URLs. New campaign and the campaign page are the same screen — creating is just its empty state.
2 – 16 September 2026 · defaults: product → Himma · every cell is the same destination, said for a different source
| Destination | ManyChat · IG | ManyChat · FB | YouTube | Newsletter | Clicks |
|---|---|---|---|---|---|
| Himma patternpat-himma → zaneteknits.com/patterns/himmaproduct · Himma · on the destination | go/ig-mc/himma-launch/pat-himma ⧉ | go/fb-mc/himma-launch/pat-himma ⧉ | go/yt/himma-launch/pat-himma ⧉ | go/nl/himma-launch/pat-himma ⧉ | 418 |
| Tutorial: lifted increasestut-lifted-inc → youtube.com/watch?v=…product · Himma · campaign default | go/ig-mc/himma-launch/tut-lifted-inc ⧉ | go/fb-mc/himma-launch/tut-lifted-inc ⧉ | go/yt/himma-launch/tut-lifted-inc ⧉ | go/nl/himma-launch/tut-lifted-inc ⧉ | 96 |
| Himma yarn at mYakmyak-himma → myak.com/collections/tibetan-cloudpartner · mYak · on the destination | go/ig-mc/himma-launch/myak-himma ⧉ | go/fb-mc/himma-launch/myak-himma ⧉ | go/yt/himma-launch/myak-himma ⧉ | go/nl/himma-launch/myak-himma ⧉ | 57 |
| LBJ course (cross-promo)course-lbj → zaneteknits.com/courses/lbjproduct · LBJ course — differs from the campaign default | go/ig-mc/himma-launch/course-lbj ⧉ | go/fb-mc/himma-launch/course-lbj ⧉ | go/yt/himma-launch/course-lbj ⧉ | go/nl/himma-launch/course-lbj ⧉ | 12 |
| + add destinations — paste one URL or a whole list; existing ones are recognised | |||||
Every link names a campaign. There is no campaign-less sentence — the untagged link is the exact thing this project exists to kill. Standing links live under campaigns marked evergreen, so “mandatory” never means “make something up”.
Mismatches are loud. Every featured destination shows its resolved relates, and a relation that differs from the campaign's default is visibly flagged — a warning to check, never a silent divergence.
A campaign is never locked. It starts with one destination and grows — a new tutorial, a partner page, the homepage — for as long as it runs.
Problem → solution 2
Destinations exist in their own right — a library of named places that campaigns borrow from, again and again. mYak's retreat page gets promoted in every campaign that's remotely related; the newsletter sign-up appears everywhere; the short-rows tutorial outlives every launch it featured in. One record each, forever.
Creating one is one paste: the slug is suggested from the naming convention, the partner is suggested from the target host, both editable — and once saved, authoritative. No URL yet (the Ravelry page that isn't published)? Save it as a placeholder and repoint it when it's real.
We own the website, so we know every pattern page. If Zanete's convention is predictable — say pat-<pattern> — we can create the whole pattern library ahead of time. Then composing a link never waits on anything: pat-himma is simply trusted to exist (and clicked once to check, as she would anyway). Parked as a follow-on; it's Zanete's naming decision that switches it on.
Problem → solution 3
The workflow test this whole design must pass: on the day she posts, Zanete does one thing — open the campaign, copy the cell for that source, paste.
Where the link actually goes differs per source, and the campaign page doesn't fight that — it just supplies the right string:
ig-mc vs fb-mc), so the platforms finally separate in the reports.Problem → solution 4
No separate system. A partner link is an ordinary link whose destination (or source) happens to be someone else's site:
myak-himma is tracked from wherever it's placed: our own site (go/zk/himma-launch/myak-himma), ManyChat, the newsletter. All of it counted, so “we sent you 500 people yesterday” becomes a fact we can show, per partner.go/myak/himma-launch/pat-himma. The source is simply them — a word registered in seconds when the partnership starts.Their URL is sacred. The visitor is forwarded to the destination exactly as stored — affiliate codes untouched, nothing of ours bolted on. All our tracking already happened on our side of the hop.
Option, per partner, off by default: add a utm_source=zaneteknits calling card so the partner sees our traffic in their own dashboard too.
Three yarn partners on the pattern page, a newsletter, and a link for mYak's own site. In sentences (names indicative until Zanete fixes her conventions): go/zk/aus2/myak-aus2 · go/zk/aus2/wrap-aus2 · go/zk/aus2/balt-aus2, the same three with nl for the newsletter, and go/myak/aus2/pat-aus2 to hand to mYak. If the build slips, short.io is the fallback — but this is Route 1's first real deadline.
Partner purchase tracking — Shopify Collabs, pixels on partner confirmation pages, revenue per partner — is real and deliberately later. It builds on these same links; nothing here has to be redone for it.
Problem → solution 5
We don't build a dashboard — every click and every purchase lands in PostHog, and these are the views it can then answer. All numbers below are illustrative; the shapes are the point.
clicks · and revenue once purchases attribute
the platform truth — “direct” finally dissolves
every source, broken down by the campaigns it drove
| source · campaign | clicks | buyers |
|---|---|---|
| 1,157 | 27 | |
| himma-launch | 612 | 9 |
| web-anniversary | 344 | 11 |
| lbj-bundle-oct | 201 | 7 |
| TikTok | 279 | 2 |
| himma-launch | 183 | 0 |
| web-anniversary | 96 | 2 |
| 230 | 7 | |
| himma-launch | 142 | 4 |
| web-anniversary | 88 | 3 |
one destination across every campaign that ever shared it
| tut-shortrows | clicks |
|---|---|
| himma-launch | 214 |
| aus2 | 187 |
| daydream-launch | 44 |
| support (evergreen) | 31 |
outbound clicks — which partners the audience actually wants
Honesty from the strategy conversation, kept: the money columns get rich when revenue lands on our site (bundles, courses, eventually pattern sales). The click columns are true from day one — and per-buyer attribution already works today for anyone who signs in, because PostHog joins their clicks to their purchase the moment they identify.
Under the surface
/go/<source>/<campaign>/<destination> — exactly three words, hyphens inside a word, slashes only between them. The link itself joins the three; nothing is stored per combination. Redirects are never cached.Build order
destinations, campaigns (with aliases), sources, plus each campaign's featured list (Neon)/go redirect: three-word parsing, placeholder auto-create for unknown source/campaign, enrichment, session-identify, off-site rules/links/tiktok, /links/instagram, … per platformRetired from the July build: the place taxonomy and picker, both admin screens, the Notion mint button, the short.io import (old links simply stay on short.io until they fade), and the coverage board.
To settle together
aus2 for Austerskorks. Worth writing the convention down once; names are permanent and appear inside every link.pat-…, rav-…, tut-…, myak-… — so a slug is readable and composable from memory. Zanete owns the scheme, and a predictable one unlocks pre-creating the whole pattern library.ig-mc · ig-story · ig-bio · fb-mc · fb-post · yt · tt · pin · nl · zk · partner words as they arise. Needs one final read-through from Zanete before seeding.