Tracked links
One short address for each post, influencer or flyer: where it leads, a QR code to print, and the clicks, orders and sales it brings.
Written 2026-09-29. A tracked link is one short address on your store for one post, influencer or flyer. When someone opens it, your store sends them to the page you picked and records where they came from, so you can see which post brings visits and sales.
https://shop.example.com/l/anna-ig leads to /products/linen-apron?utm_source=instagram&utm_medium=social&fh_link=anna-ig.
Everything here is live data: a link works as soon as it is saved.
1. Fields and rules
| Field | Rule |
|---|---|
name | Your own name for it, e.g. "Anna, Instagram bio". 1 to 80 characters. Shoppers never see it. |
code | The short address after /l/. 4 to 24 characters: a to z, 0 to 9 and single hyphens, no hyphen at either end. Unique in your store. Left out, one is made from the name. Typed in capitals, it is saved in lower case. |
targetPath | Where it goes, a page of your store: / (home), /products/<handle>, /collections/<handle> or /pages/<handle>. The page must be one shoppers can see. No query string, no other link, never an outside address. |
channel | Where you share it. Fills source and medium (table below). |
source, medium, campaign | Lower case letters, numbers, dots, hyphens and underscores, up to 60 each. source is required (the channel fills it). |
discountCode | Optional. An existing, active code shoppers can use now, not a template or a one-shopper code. Applied in the cart on arrival. |
active | On by default. Off pauses the link. |
| Limit | 500 links a store. |
The short address cannot change once the link is made: it may already be printed on a flyer or pinned in a bio. Everything else can change. To use another address, make a new link.
Reserved words. These codes are refused: edit, admin, link, links, null, undefined, none, true, false, test, home, index, cart, checkout, account, login, logout, signup, register, password, settings, orders, track, products, collections, pages, blog, search, media, assets, static, favicon, well-known, robots, sitemap, qr-code, qrcode, formahand. (new and api are reserved too, and too short anyway.)
Discount codes that end. If the code has expired, is paused or is used up when a shopper clicks, the link still works and simply arrives without it.
Channels
| Channel | source | medium |
|---|---|---|
instagram | social | |
facebook | social | |
| TikTok | tiktok | social |
| YouTube | youtube | social |
pinterest | social | |
| X | x | social |
whatsapp | social | |
| Email newsletter | newsletter | email |
| Influencer | influencer | referral |
| Flyer or print | print | offline |
| QR code in store | qr | offline |
| Other | other | link |
The channel ids for tools and routes are the sources above (instagram … other). Give source or medium yourself to override the channel's.
2. What happens on a click
| Someone opens | Your store answers |
|---|---|
| An active link | 302 to its page on the same address they used, with utm_source, utm_medium, utm_campaign (each only when set) and fh_link=<code>. Anything they added to the short address is dropped. |
| An active link with a usable discount code | the same, and the code goes into their cart |
| A paused link, an unknown code, a malformed address | 302 to the home page, with no parameters. Never an error page. |
HEAD | the same answer as a visit, not counted |
| Any other method | 405 |
Every answer says noindex, so search engines do not list short addresses, and no-store, so nothing caches them.
3. What is counted, and what is not
Clicks. One counter per link per day (UTC). The list shows every click and the last 30 days. Bots and browser prefetches are not counted.
Nothing about the visitor is stored. No address, no browser details, no referrer. A tracked link sets no new cookie. When the link carries a discount code, the code goes into the cart's own discount cookie, the one a typed code uses.
Orders and revenue per link come from your store's sales sources: the order remembers the fh_link it arrived with, and this respects the shopper's tracking choice. Only paid orders count. If sales cannot be read for a moment, the list shows a dash for orders and revenue, and the tools answer salesKnown: false. Clicks work from the first day.
4. QR code
Every link has a QR code, made in the dashboard itself: nothing is sent anywhere to draw it.
| Downloads | PNG, 1024 px square, and SVG |
| Error correction | M |
| Size | versions 1 to 10, addresses up to 213 bytes |
| Border | 4 modules of white on every side |
For print: keep the white border around the code. Test it with a phone before you print a batch.
5. Dashboard
Analytics → Tracked links. The list shows each link's name, short address, clicks, orders and revenue, with Copy and QR buttons; the row's menu has Edit, Pause (or Resume) and Delete, which asks first. A new store has none: the screen says what a link is and offers New link.
New link takes three steps:
- Where it goes. Pick the home page, a product, a collection or a page.
- Who it is for. A name, the channel, an optional campaign. The short address is suggested from the name; change it now or never.
- Discount (optional). Pick an active code.
The last screen shows the short address and its QR code, ready to copy or download.
6. Routes
GET /api/tracked-links lists every link, newest first; ?id= or ?code= answers one. PUT /api/tracked-links creates a link, or changes one when the body has its id (only the fields given change). DELETE /api/tracked-links?id= or ?code= removes one. storefront:read / storefront:write.
{ "name": "Anna, Instagram bio", "code": "anna-ig", "targetPath": "/products/linen-apron",
"channel": "instagram", "campaign": "spring", "discountCode": "ANNA10" }
Answer (201 when made, 200 when changed):
{ "link": { "id": "8c1f…", "code": "anna-ig", "name": "Anna, Instagram bio",
"targetPath": "/products/linen-apron", "source": "instagram", "medium": "social",
"campaign": "spring", "discountCode": "ANNA10", "active": true,
"createdByKind": "person", "createdBy": "…",
"createdAt": "2026-09-29T10:00:00.000Z", "updatedAt": "2026-09-29T10:00:00.000Z" } }
The list answers { links, limit, channels, hosts, currency, salesKnown }; each link adds clicks, clicksLast30Days, orders and revenueMinor (minor units of currency). A refusal is a 400 with errors: [{ field, error }] naming every field at once; a code already in use is a 409.
7. Tools
list_tracked_links { limit?, offset?, id?, code? }: links with a ready-to-shareurl, clicks, orders andrevenueMinor, 25 a page (up to 100). Read-only.upsert_tracked_link { id?, code?, name, targetPath, channel?, source?, medium?, campaign?, discountCode?, active? }: withoutidit makes a link; withidonly the given fields change, so{ id, active: false }pauses one.delete_tracked_link { id | code }: the short address then leads home and its click history is removed. To stop a link and keep its history, pause it instead.
8. Events
Every change is in the store's event log (events.md): tracked_link.created, tracked_link.updated and tracked_link.deleted. The resource id is the link id.
See also
- analytics.md: your store's numbers.
- order-sources.md: where every paid order came from.
- checkout-rules.md: discount codes, the kind a link can carry.
- content-seo.md: redirects, for an address you moved rather than one you share.
- agents.md: every tool, with its scope.