Link tracking · spec — fifth pass · 26 Aug 2026

The link is a sentence:
source / campaign / destination.

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

Four things are true today

“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 affiliates

Behind 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

Three words, one link

Source

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.

Campaign

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.

Destination

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.

The link, read aloud

zaneteknits.com/goour site — where the enrichment happens
/
ig-mcsource: Instagram, via ManyChat
/
himma-launchcampaign: the push
/
myak-himmadestination: mYak's Himma yarn page

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.

The sentence rule

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.

Decision — a typo never punishes the visitor

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.

Product and partner: on the destination, defaulted by the campaign

The permanence rule

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 click, step by step

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.

1 · The click

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.

2 · Our site enriches it

The three words are read against the registers, and one lookup fills in the whole story:

sourceInstagram · ManyChat · the 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.

3 · PostHog is told
link_click source: ig-mc campaign: himma-launch destination: myak-himma product: himma partner: myak person: asim@example.com
4 · The visitor moves on

Forwarded to myak.com/collections/tibetan-cloudexactly 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

A campaign is a page, not a memory

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.

screenCampaigns
  • list of campaigns, running first
  • + new campaign
screenNew campaign
  • name → himma-launch
  • dates (start · end) — or evergreen
  • description — what this push is
  • defaults: product · partner (optional)
  • destinations: paste URLs, or pick existing
  • create
screenCampaign page
  • the matrix: destinations × sources
  • copy any cell
  • + add destinations (any time)
  • relates per destination, mismatches warned
  • clicks so far, per destination

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.

himma-launchmockup

2 – 16 September 2026 · defaults: product → Himma · every cell is the same destination, said for a different source

DestinationManyChat · IGManyChat · FBYouTubeNewsletterClicks
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
The rules

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 are a library, not a byproduct

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.

screenDestination library
  • every destination — pat-…, tut-…, myak-…
  • + new — paste the URL (or none yet)
  • slug + partner suggested, editable
screenThe destination
  • compose a link: pick source · campaign → copy
  • QR code of the composed link (for print)
  • edit product · partner
  • repoint the URL (slug never changes)
Unlocked by the naming convention — pre-created destinations

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

Posting day is a copy button

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.

screenContent plan (Notion)
  • tomorrow: Himma reel, Instagram
  • which campaign? → himma-launch
screenCampaign page
  • copy the ManyChat · IG cell
screenThe platform
  • paste into ManyChat / description / pin

Where the link actually goes differs per source, and the campaign page doesn't fight that — it just supplies the right string:

Problem → solution 4

Partner links — this is the affiliate tracking

No separate system. A partner link is an ordinary link whose destination (or source) happens to be someone else's site:

What an off-site destination receives

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.

First outing — Austerskorks, live by Tue 1 September

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

What you'll be able to see

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.

By campaignillustrative

clicks · and revenue once purchases attribute

himma-launch1,204 · £742
web-anniversary871 · £1,105
lbj-bundle-oct553 · £980
newsletter (evergreen)402
By sourceillustrative

the platform truth — “direct” finally dissolves

Instagram · ManyChat1,310
Newsletter804
TikTok bio507
YouTube330
Source × campaignillustrative

every source, broken down by the campaigns it drove

source · campaignclicksbuyers
Instagram1,15727
himma-launch6129
web-anniversary34411
lbj-bundle-oct2017
TikTok2792
himma-launch1830
web-anniversary962
Facebook2307
himma-launch1424
web-anniversary883
By destinationillustrative

one destination across every campaign that ever shared it

tut-shortrowsclicks
himma-launch214
aus2187
daydream-launch44
support (evergreen)31
By partnerillustrative

outbound clicks — which partners the audience actually wants

mYak238
Rauwerk141
Baltic Knits83

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

The plumbing, briefly

Build order

Two routes, then later

Route 1Trackable links + campaigns · Austerskorks live by 1 Sep

  • Tables: destinations, campaigns (with aliases), sources, plus each campaign's featured list (Neon)
  • The /go redirect: three-word parsing, placeholder auto-create for unknown source/campaign, enrichment, session-identify, off-site rules
  • Admin: campaign page (paste-box add · matrix · mismatch warnings) · destination library · the destination page with its link composer + QR
  • PostHog: the click event, the saved views above, the buyer-merge check

Route 2The /links pages

  • Our own linktree: /links/tiktok, /links/instagram, … per platform
  • A campaign checkbox controls what shows; evergreen links on top; copy and imagery per campaign so the page can be beautiful, not just a list
  • This is where TikTok and Instagram bio traffic finally gets attributed

LaterDeliberately parked

  • Pre-created destinations for every pattern page — switched on by Zanete's naming convention
  • Partner purchase tracking: Shopify Collabs, pixels on partner confirmation pages, revenue per partner
  • One-way Neon → Notion mirror of campaigns

What July taught us — carried in as requirements

Retired 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

Open questions

Campaign naming. Zanete leans short acronyms — aus2 for Austerskorks. Worth writing the convention down once; names are permanent and appear inside every link.
Destination naming. Hyphenated prefixes — 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.
The source list. Largely decided in the P2 review: 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.