Attribution
How Referalli decides which partner gets credit for a customer, and how to configure and replay it.
Attribution runs once per customer, inside POST /v1/referrals/associate. It gathers every candidate signal for that customer, applies the program's model and window, and either names a winning partner or returns no match. The decision and its reasoning are stored on the referral, and they can be recomputed at any time from the raw facts.
Channel priority
The engine tries four channels in a fixed order and stops at the first that produces a match:
Order |
| Signal |
|
|---|---|---|---|
1 |
|
| 1 |
2 |
| Clicks tied to this customer: the click named by | 1 |
3 |
|
| 0.9 |
4 |
| No referral token or cookie, but clicks in the last 72 hours share the customer's exact hashed IP and user agent. Requires | 0.6 |
The email-hash channel only sees clicks that were stored with a visitor id: clicks from ?via= landings handled by the tracking script and clicks recorded with anonId through POST /v1/track/events.
Fingerprint matches
What happens to a probabilistic match depends on the program's probabilisticPolicy setting (see Program settings):
| Effect |
|---|---|
| The referral is created with status |
| The referral is created |
| No referral is created; |
Partners who can't win
Before the channels run, associate drops candidates who can't be credited, and the next eligible channel or touch wins instead. Each skipped partner adds a line to reasoning.notes.
Partner | Why |
|---|---|
| Banned partners earn nothing new, so their links and coupons stop attributing new customers. A |
Flagged by the platform | Same as banned. |
| The customer is that partner, and nobody is credited for referring themselves. |
This applies to live associate calls only. A referral that already exists keeps its partner, and unbanning a partner doesn't change any earlier decision. Replays don't apply it either.
When every candidate is skipped, referral is null and the response says why:
{ "referral": null, "reasoning": { "notes": ["partner ptr_... is banned; their touches were skipped"] } }Self-referrals
When the customer's emailHash equals the hash of the winning partner's own email, it's a self-referral, whatever the channel (coupon, click, email hash or fingerprint). The program's selfReferralPolicy decides what happens:
| Effect |
|---|---|
| The referral is created |
| The self-referring partner is skipped like a banned one. Another eligible partner can still win; otherwise |
Eligible clicks and the cookie window
For the click-based channels, a click counts only if it is:
eligible, meaning its fraud score is below 70 (see Links and clicks), and
inside the window: no more than
cookieWindowDaysdays before the signup, and not after it.
Excluded clicks are listed in reasoning.notes, for example "2 outside the 60-day window" or "1 excluded by fraud filtering".
Burner emails and datacenter traffic
Referalli checks two signals itself, on top of what an auth platform sends in risk:
Burner emails. A sign-up whose email domain is on Referalli's disposable-email list (about 10,000 domains, from the community-maintained disposable-email-domains list, and their subdomains) is created as
pending_reviewwith anidentity_riskfraud case. The domain comes fromemailon an Atlas sign-up, or fromemailDomainonassociateand on sign-ups that send only a hash. Turn it off per program withriskPolicy: { "holdDisposableEmail": false }. Each case also lowers the referring partner's quality score.Datacenter IPs. A click from a published cloud or hosting range (AWS, Google Cloud, Azure, DigitalOcean, OVH, Hetzner, Linode, Vultr) adds 35 to its fraud score. That alone doesn't make it ineligible, but combined with another signal (high click velocity, for example) it crosses 70.
Both lists ship with Referalli and are refreshed with each release.
Partners banned elsewhere
When a partner account has been banned by at least two other merchants, or flagged by Referalli, the partner (GET /v1/partners/{id}) and enrollment lists carry networkWarning:
{ "flagged": true, "reason": "banned_by_other_merchants", "bannedByOtherMerchants": 2 }It never says which merchants, and a single ban elsewhere isn't revealed (bannedByOtherMerchants stays 0). Your own ban doesn't count. The dashboard shows it as a badge on the partner page and on pending applications. It's a warning only: Referalli doesn't block the partner in your org.
Attribution models
When several eligible clicks qualify, the program's attributionModel assigns each a weight. The click with the highest weight wins; ties go to the most recent click. All weights are stored in touchCredits.
Model | Weights |
|---|---|
| The most recent click gets 1, the rest 0. |
| The earliest click gets 1, the rest 0. |
| First and last clicks get 0.4 each and the middle clicks share the remaining 0.2. With two clicks, 0.5 each. |
| Each click's weight halves every 7 days of age, then weights are normalized to sum to 1. |
Set the model and windows on the program:
curl -X PATCH https://referalli.com/v1/programs/prg_... \
-H "Authorization: Bearer $KEY" \
-H "content-type: application/json" \
-d '{"attributionModel": "time_decay", "cookieWindowDays": 30}'Field | Range | Default | Effect |
|---|---|---|---|
| see above |
| How multiple clicks are weighted. |
| 1 to 730 | 60 | How far back before signup a click can count. |
| 1 to 1095 | 365 | How long after signup payments still earn commission. |
Program settings
Six more settings live on the program and are set with the same PATCH /v1/programs/{id} call (owner or admin). Send only the ones you want to change; the others keep their values. GET /v1/programs/{id} returns all six under settings, with defaults filled in.
curl -X PATCH https://referalli.com/v1/programs/prg_... \
-H "Authorization: Bearer $KEY" \
-H "content-type: application/json" \
-d '{
"probabilisticPolicy": "auto",
"selfReferralPolicy": "reject",
"splitCommissions": true,
"allowPaidSearch": false,
"challengeLinks": true,
"utmTemplate": { "utm_source": "partner", "utm_campaign": "{{code}}" }
}'Field | Type | Default | Effect |
|---|---|---|---|
|
|
| What a fingerprint-only match does (above). |
|
|
| What a self-referral does: hold it for review, or skip that partner (Self-referrals). |
| boolean |
| When on and a referral's |
| boolean |
| When off, a |
| boolean |
| When on, a |
| object |
| UTM parameters added to every |
allowPaidSearch, challengeLinks and utmTemplate apply to /go redirects only, not to ?via= landings handled by the tracking script. A value of the wrong shape returns 400 validation_error.
Explain a decision
Every referral stores reasoning: the model, the number of touchpoints considered, the window and notes. GET /v1/referrals/{id}/journey returns the referral with each touchpoint's timestamp, URL, fraud score, eligibility, weight, and which one won. Use it to answer "why did this partner get the credit".
Replay attribution
Raw facts (clicks and associations) are append-only. Referrals are derived from them, so after changing a program's model or window you can recompute every referral in the program:
curl -X POST https://referalli.com/v1/attribution/replay \
-H "Authorization: Bearer $KEY" \
-H "content-type: application/json" \
-d '{"programId": "prg_..."}'
# { "recomputed": 412, "changed": 9 }Replay re-runs the engine for each customer as of their original signup time, using the program's current settings. It is deterministic: running it twice in a row changes nothing the second time. It only revisits customers that already have a referral; it never creates new ones. Referrals that were reassigned by hand (matchedVia: "manual") are left alone. Replay updates who the referral belongs to going forward; commission lines already earned are not moved.