Attribution Mechanics

How Affitor assigns credit to partners — the cookie, the click ID, the attribution windows, and what happens on re-click.

Beta — The Affitor SDKs and CLI are in active development. The documented happy-path works; edge cases may change. Report issues on GitHub.

When a visitor clicks an affiliate link, Affitor creates a customer record for that click and the SDK writes a first-party cookie with its click_id. By default, the partner whose click created the customer keeps that customer: a later click from another partner in the same browser is logged, but it does not take the customer over. Leads and sales for that customer resolve to that first partner.


Attribution model

SettingDefaultRangeDescription
Attribution window60 days1 – 365Maximum age of the click at the customer's signup. Also used as the cookie lifetime

Each program has one attribution window, set in your program settings. The click endpoint returns it as cookie_window_days, and the SDK uses it as the lifetime of the cookie it writes, so no SDK update is needed when you change the value in the dashboard.

Program settings have no option for last-click or multi-touch (linear) credit. Credit goes to the partner whose click created the customer, as described above.


When a visitor lands on a URL containing ?aff=<code>, the SDK calls POST /api/v1/track/click and stores two first-party cookies:

CookiePurpose
affitor_click_idThe primary attribution token. Passed to lead and sale calls.
affitor_aff_urlThe full landing URL used to detect partner changes on re-click.

A legacy customer_code cookie is written alongside affitor_click_id for backwards compatibility with older integrations. On read, if only customer_code exists, the SDK migrates its value into affitor_click_id automatically.

The cookie lifetime is the program's attribution window (default 60 days), taken from the cookie_window_days value in the click response.


The SDK auto-detects the broadest writable domain so attribution follows a visitor across subdomains (e.g. app.example.com and www.example.com share the same cookie).

Detection algorithm:

  1. Split window.location.hostname into parts.
  2. Starting from the registrable domain (e.g. .example.com), attempt to write a probe cookie with each candidate domain.
  3. The first domain that accepts the write becomes the cookie domain for the session and is cached in cookieDomain.
  4. localhost and bare IP addresses are excluded — no domain is set, so the cookie is host-only.

You can skip auto-detection by setting the cookie domain on the tracker script tag:

<script
  src="https://api.affitor.com/js/affitor-tracker.js"
  data-affitor-program-id="123"
  data-affitor-cookie-domain=".example.com"
></script>

The @affitor/sdk types do not include a cookie domain option (loadAffitor() takes programId plus debug, env and scriptUrl; init() takes only programId and debug), so use the script attribute when you need one.


Re-click from another partner

If a visitor already has an affitor_click_id cookie and clicks a different partner's link, the SDK sends the new click together with the existing click_id (as existing_click_id). The server finds the customer that already owns that click_id and keeps it: the new click is still recorded and counted for the new partner (a click event, plus that link's and partner's click counts), but the customer, its click_id and its partner do not change, so later leads and sales stay with the original partner.

ScenarioOutcome
No existing cookie, ?aff= presentNew click tracked, new customer created, new affitor_click_id written
Same partner link clicked againExisting cookie reused — no new click event
Different partner link clickedNew click counted for the new partner; the customer and its click_id stay with the original partner
No ?aff= in URL, cookie presentExisting attribution restored from cookie silently
No ?aff= in URL, no cookieNo click_id. A lead call without a click_id or a known customer_key returns 400 and nothing is recorded

How the attribution window is checked

There is one window per program (default 60 days). It is used in two places:

  • Cookie lifetime — the affitor_click_id cookie expires after that many days.
  • Click → signup check — when the customer's first payment is attributed, the click must be at most that many days older than the customer's signup. If no signup was recorded, the click's age is measured at that first payment instead.

The check runs once, at the customer's first payment. A customer who signed up inside the window can buy later and still be attributed, and once the first payment is attributed, later renewals and repeat purchases are not checked against the window again.

Click ──── at most N days ────► Signup ─────────────► First payment (window checked here)
                                                        │
                                                        └──► Renewals (not re-checked)

Recurring revenue attribution

For subscription products, every recurring charge can generate a commission within the commission duration below. Once the first payment is attributed, the attribution window is not checked again for renewals.

Commission typedefault_duration_monthsBehavior
cps_recurringe.g. 12Commission paid for up to N months of renewals
cps_lifetimenullCommission paid on every renewal indefinitely
cps_one_time0Commission paid once on the first payment only

The Stripe webhook handler uses is_recurring: true and subscription_id on the /api/v1/track/sale call to match renewals back to the original attributed partner. No action is required from the SDK on renewal — Stripe autocapture handles it automatically when the webhook is connected.


Program-level attribution fields

These program fields control attribution and commissions. The attribution window is set as one number in your program settings:

FieldTypeDefaultDescription
attribution_window_daysinteger60Maximum click age at signup, and the cookie lifetime (1–365 days)
default_commission_typeenum—cpc, cpl, cps_one_time, cps_recurring, cps_lifetime
default_duration_monthsinteger—Recurring commission duration; null = lifetime, 0 = one-time
default_hold_period_daysinteger15Days a commission stays in hold before becoming payable (max 31)

© 2026 Affitor