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
| Setting | Default | Range | Description |
|---|---|---|---|
| Attribution window | 60 days | 1 – 365 | Maximum 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.
The affitor_click_id cookie
When a visitor lands on a URL containing ?aff=<code>, the SDK calls POST /api/v1/track/click and stores two first-party cookies:
| Cookie | Purpose |
|---|---|
affitor_click_id | The primary attribution token. Passed to lead and sale calls. |
affitor_aff_url | The 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.
Cross-subdomain cookie sharing
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:
- Split
window.location.hostnameinto parts. - Starting from the registrable domain (e.g.
.example.com), attempt to write a probe cookie with each candidate domain. - The first domain that accepts the write becomes the cookie domain for the session and is cached in
cookieDomain. localhostand 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.
| Scenario | Outcome |
|---|---|
No existing cookie, ?aff= present | New click tracked, new customer created, new affitor_click_id written |
| Same partner link clicked again | Existing cookie reused — no new click event |
| Different partner link clicked | New click counted for the new partner; the customer and its click_id stay with the original partner |
No ?aff= in URL, cookie present | Existing attribution restored from cookie silently |
No ?aff= in URL, no cookie | No 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_idcookie 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 type | default_duration_months | Behavior |
|---|---|---|
cps_recurring | e.g. 12 | Commission paid for up to N months of renewals |
cps_lifetime | null | Commission paid on every renewal indefinitely |
cps_one_time | 0 | Commission 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:
| Field | Type | Default | Description |
|---|---|---|---|
attribution_window_days | integer | 60 | Maximum click age at signup, and the cookie lifetime (1–365 days) |
default_commission_type | enum | — | cpc, cpl, cps_one_time, cps_recurring, cps_lifetime |
default_duration_months | integer | — | Recurring commission duration; null = lifetime, 0 = one-time |
default_hold_period_days | integer | 15 | Days a commission stays in hold before becoming payable (max 31) |