Skip to main content
Attribution answers one question for every order: which creator’s click earned this sale? This page explains how a click is recorded, how it travels to checkout, how an order is matched back to it, and what happens when no click qualifies. For the plain-language version, see How credit is decided.
The Freeq Growth Network is last-click. The most recent eligible click before the purchase wins. We identify both the first click and the last click: the last click earns the credit, and the first click is recorded for display (see First-touch display).

1. Minting a click

Every creator link resolves through the public redirect, served without the /api prefix: Each request:
  1. Looks up the campaign destination. Unknown codes and campaigns return 404.
  2. Mints a click id of the form clk_<uuid v4>.
  3. Writes one click record (UserClickProfile): click id, campaign, creator address (lower-cased) and a derived pseudo-address (a hash of the click id, used as the “user” on-chain).
  4. Builds the destination URL with tracking parameters:
    • uam_click=<click id> (the primary carrier)
    • utm_source=uam, utm_medium=affiliate
    • utm_term=<click id> (a copy that survives Shopify’s journey data, which strips non-UTM parameters)
  5. Optionally allocates a discount code (below), then responds 302.
Nothing is written on-chain at click time. The redirect is rate limited per IP with a ceiling far above human traffic.

Per-click discount codes

When a brand enables codes for a campaign and the store’s provider supports writing discounts, the redirect mints a single-use code shaped UAM-XXXX-XXXX-XXXX for this click, valid for a short time (45 minutes by default). The redirect is wrapped in the provider’s apply-and-redirect URL so the code is already in the cart. These are not vanity codes; each one maps to exactly one click. If the code cannot be issued (budget or cap exhausted, campaign ended), the shopper is still redirected with the click recorded, plus uam_coupon=unavailable so the storefront can show a message. If code issuance errors or times out, the redirect fails open to the plain tracked URL.

Landing-path override

Some partner providers allow a link to land on a specific page: /go/<code>?to=/<relative path>. The path must be relative, same-origin and inside that provider’s allowlist of path prefixes. Anything else falls back to the campaign destination without error. Attribution is unaffected either way, because the click is minted before the landing path is resolved.

2. Carrying the click to checkout (Shopify)

URL parameters are fragile: Shopify normalises landing pages and may drop unknown parameters. The theme app embed installed with the Shopify app makes the click durable. On every page load it:
  • reads uam_click from the URL and accepts it only if it matches clk_<uuid v4>;
  • overwrites the cart attribute uam_click with it (this reaches the order as note_attributes);
  • injects a hidden line-item property _uam_click into product forms, so the click is stamped on each line at add-to-cart and on unbranded “Buy it Now”;
  • mirrors the click to localStorage and re-applies it if the cart is reset.
The overwrite is what makes the system last-click: if a shopper lands from creator A and later from creator B, the cart now carries B. Items already in the cart keep their earlier line stamp, so one order can carry several clicks; the resolver handles that (below). The embed is fire-and-forget and never blocks or alters the storefront. One permanent gap: branded wallet buttons (Shop Pay, PayPal, Apple Pay) render in frames the embed cannot reach, so a branded “Buy it Now” carries no line stamp and relies on the cart attribute or the weaker rungs.

3. Resolving a Shopify order

An order webhook is accepted if it carries any carrier the platform attached: uam_click in the landing URL, in note_attributes, in a line-item property, or a UAM- discount code. Orders with none are kept only when they came from the online store (source_name = web), as recovery candidates; everything else (POS, drafts, API orders) is ignored. Resolution is a ladder that stops at the first rung that yields a click: Merchant-owned codes (for example SUMMER20) are not a signal and never block the lower rungs. A customer’s email is not a rung; it is associated with the click only after resolution.

Rung 2: newest eligible click wins

All candidate click ids are collected and de-duplicated. A candidate is eligible when its campaign exists, belongs to this order’s store, and promotes a product that is in the order. The winner is the eligible click with the newest creation time (ties go to the click in the cart attribute, then the lower id).
  • No candidates: fall through to rungs 3 and 4.
  • Candidates exist but none are known: UNATTRIBUTED (CLICK_NOT_FOUND).
  • Known but none eligible: UNATTRIBUTED with the reason of the newest one (CAMPAIGN_NOT_ACTIVE, STORE_MISMATCH or PRODUCT_NOT_IN_CAMPAIGN).
  • Admin API data incomplete: no winner is picked; the order stays pending and is retried.
  • Implausibly many distinct clicks: UNATTRIBUTED (DURABLE_CANDIDATES_EXCEEDED). A winner is never chosen from a truncated set.

Rung 4: URL freshness

The landing URL can be stale (a browser restoring a days-old page). An optional freshness gate accepts a URL click only if it was minted shortly before the order (60 minutes by default) and not after it; otherwise the order is UNATTRIBUTED (CLICK_TOO_OLD).

4. Other providers: the neutral path

Non-Shopify providers do not send Shopify payloads. Their adapters normalise each order to a neutral input (clickIdCandidates, line items with external product ids, occurredAt, and optional buyer identity) and the engine resolves it without any journey lookup or URL parsing:
  1. A platform discount code stored on the order row, if any.
  2. Each entry of clickIdCandidates, in order. A candidate from another store is skipped.
  3. Nothing left: UNATTRIBUTED.
Every resolution then passes a central store guard. See Commerce providers.

5. After a click is chosen

The same checks run for both paths before any money moves:
  • Product match. The campaign’s product must be in the order (store-wide campaigns excepted).
  • Self-referral. If the provider supplies a buyer identity and it resolves to the very creator being credited: SELF_REFERRAL, no commission.
  • Returning customer. A buyer whose normalised email already produced a paid order in the same campaign is tracked but not paid: INELIGIBLE_RETURNING_BUYER. Providers can send their own authoritative first-time-buyer flag instead.
  • Budget and membership. The campaign must cover the commission and the creator must be a campaign member on-chain.
The commission base is frozen on the order before anything is sent. It is the order total (after discounts, including tax and shipping) for every campaign. For a single-product campaign the product only decides whether the order qualifies.

6. On-chain writes and retries

Only after attribution resolves does the backend write the chain, in order: recordUserMovement (the winning click) then registerConversion. Each conversion goes through one durable submission record: intent before sending, the broadcast hash after, and a reconcile step on every retry. That prevents a crash from paying the same sale twice. Escrow and ledger entries follow a successful conversion. See Money path. A recovery poller sweeps orders left pending: never started, stuck mid-flight, or waiting for Shopify’s journey data to become ready. Orders still pending after 24 hours expire as UNATTRIBUTED (NOT_CONFIRMED_IN_24H). Orders with an in-flight chain submission are never expired.

7. First-touch display

For Shopify online-store orders, a background job later reads the journey’s first visit and, if it carries a click, stores it for display (“first touch”). It typically appears some minutes after the order, once Shopify’s journey data is ready. First touch never changes attribution: the last eligible click always wins the order, and no attribution, escrow, ledger, settlement or on-chain path reads the display fields. Separately, the campaign’s reward mode (rewardMode, locked when the campaign is activated) decides how the winning order’s commission is paid:
  • LAST_CLICK_ONLY: the last-click creator receives the whole order commission.
  • FIRST_LAST_80_20 (Shopify only): if another eligible creator first introduced the shopper, they receive 20% of the order commission and the last-click creator the remaining 80%. The discovery resolver turns the first touch into a frozen per-order decision once; the money path reads that decision, never the display fields. The 20% share is floor(hold × 2000 / 10000) in minor units and the last-click creator gets the remainder.
The resolver also records a first-touch point (internally a discovery point) for the first-touch creator in either mode. Points are a ranking signal only, never money.

8. Order outcomes and reasons

Not-paid orders carry a stable reason code. Readers must tolerate codes added later and null on older rows. The authoritative list is ATTRIBUTION_REASON_CODES in src/modules/commerce/domain/attribution-reason.ts.