Referral Attribution Flow
Complete flow for tracking, attributing, and crediting referrals to partners.
Overview
The referral attribution system tracks:
- Referral link clicks and cookie management
- Attribution capture on registration
- Attribution capture on purchases (including guest)
- Attribution window and rules
- Multi-touch attribution considerations
- Data storage and reporting
Main Flow Diagram
Step Details
1. Referral Link Structure
Standard Link Format:
https://platform.com/{targetPath}?ref={partnerCode}&utm_source={source}&utm_medium={medium}&utm_campaign={campaign}Components:
| Component | Required | Example | Description |
|---|---|---|---|
| ref | Yes | ABC123XY | Partner's referral code |
| utm_source | No | Traffic source | |
| utm_medium | No | bio | Marketing medium |
| utm_campaign | No | winter_2024 | Campaign identifier |
Example Links:
# Standard referral
https://platform.com/?ref=ABC123XY
# With UTM tracking
https://platform.com/products?ref=ABC123XY&utm_source=instagram&utm_medium=post&utm_campaign=winter_sale
# Custom landing page
https://platform.com/lp/special-offer?ref=ABC123XY
# Short link (via link shortener)
https://iwm.link/r/ABC123XY2. Cookie Management
Cookies Set on Referral Click:
| Cookie Name | Value | Duration | HttpOnly | Secure |
|---|---|---|---|---|
iwm_ref_code | Partner code | 30 days | No | Yes |
iwm_ref_link_id | Link UUID | 30 days | No | Yes |
iwm_first_touch | ISO timestamp | 30 days | No | Yes |
iwm_last_touch | ISO timestamp | Session | No | Yes |
iwm_utm | JSON (base64) | Session | No | Yes |
Cookie Setting Logic:
interface AttributionCookies {
refCode: string;
linkId?: string;
firstTouch: string; // ISO date
lastTouch: string; // ISO date
utm: {
source?: string;
medium?: string;
campaign?: string;
content?: string;
term?: string;
};
landingPage: string;
}
function setAttributionCookies(params: URLSearchParams): void {
const refCode = params.get("ref");
if (!refCode) return;
const existing = getExistingCookies();
const now = new Date().toISOString();
// First-click attribution: Don't overwrite if exists
if (!existing.refCode) {
setCookie("iwm_ref_code", refCode, { days: 30 });
setCookie("iwm_first_touch", now, { days: 30 });
}
// Always update last touch
setCookie("iwm_last_touch", now, { session: true });
// Store UTM params
const utm = {
source: params.get("utm_source"),
medium: params.get("utm_medium"),
campaign: params.get("utm_campaign"),
content: params.get("utm_content"),
term: params.get("utm_term"),
};
setCookie("iwm_utm", btoa(JSON.stringify(utm)), { session: true });
}Cookie Renewal:
iwm_ref_code: Extended to 30 days on each visit with ref paramiwm_last_touch: Updated on every visit (if cookie exists)
3. Click Tracking
Endpoint: POST /attribution/track-click
Request:
{
"partnerCode": "ABC123XY",
"linkId": "uuid-if-known",
"landingPage": "/products/featured",
"referrer": "https://instagram.com",
"utm": {
"source": "instagram",
"medium": "post",
"campaign": "winter_sale"
},
"fingerprint": {
"userAgent": "Mozilla/5.0...",
"screenResolution": "1920x1080",
"timezone": "Europe/Moscow",
"language": "ru-RU"
}
}Anonymous Attribution Record:
INSERT INTO mlm.anonymous_attributions (
cookie_id,
partner_id,
link_id,
first_touch_at,
last_touch_at,
landing_page,
referrer_url,
utm_source,
utm_medium,
utm_campaign,
ip_address,
user_agent,
fingerprint_hash
) VALUES (...);Click Deduplication:
- Same IP + User Agent within 1 hour = ignored
- Fingerprint hash used for more accurate dedup
4. Attribution Capture on Registration
Process:
- Read attribution cookies from request
- Validate partner code exists and is active
- Create user record
- Create referral_attribution linking user to partner
- Update denormalized counters
Attribution Record Created:
{
"id": "uuid",
"userId": "user-uuid",
"partnerId": "partner-uuid",
"linkId": "link-uuid-or-null",
"attributionType": "FIRST_TOUCH",
"firstTouchAt": "2024-01-10T10:00:00Z",
"lastTouchAt": "2024-01-15T14:30:00Z",
"convertedAt": "2024-01-15T14:30:00Z",
"cookieId": "anon-cookie-id",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"utmSource": "instagram",
"utmMedium": "post",
"utmCampaign": "winter_sale"
}Counter Updates:
-- Update referral link stats
UPDATE mlm.referral_links
SET registrations_count = registrations_count + 1
WHERE id = link_id;
-- Update partner stats
UPDATE mlm.partners
SET direct_referrals_count = direct_referrals_count + 1
WHERE id = partner_id;5. Attribution Capture on Purchase
For Registered Users:
async function getPartnerForOrder(userId: string): Promise<string | null> {
// Check user's referral attribution
const attribution = await db.referralAttribution.findUnique({
where: { userId },
});
return attribution?.partnerId ?? null;
}For Guest Purchases:
async function getPartnerForGuestOrder(
sessionId: string,
cookies: AttributionCookies,
): Promise<string | null> {
// Try to find from cookies
if (cookies.refCode) {
const partner = await db.partner.findUnique({
where: { referralCode: cookies.refCode },
});
return partner?.id ?? null;
}
// Try to find from cart
const cart = await db.cart.findFirst({
where: { sessionId },
});
return cart?.referringPartnerId ?? null;
}Order Attribution Storage:
-- Order stores the referring partner
CREATE TABLE product.orders (
...
referring_partner_id UUID REFERENCES mlm.partners(id),
...
);6. Attribution Window Rules
Default Configuration:
| Setting | Value | Description |
|---|---|---|
| Cookie Duration | 30 days | Time attribution cookie is valid |
| Attribution Model | First-touch | First referrer gets credit |
| Click Window | 30 days | Time from click to conversion |
| Multi-purchase | Lifetime | User always attributed to original partner |
First-Touch vs Last-Touch:
| Model | Description | When Used |
|---|---|---|
| First-Touch | First referrer gets full credit | Default for registrations |
| Last-Touch | Last referrer gets full credit | Can be configured |
| Linear | Split credit among all touchpoints | Enterprise feature |
Attribution Logic:
function determineAttribution(
existingCookie: string | null,
newRefCode: string | null,
model: "FIRST_TOUCH" | "LAST_TOUCH",
): string | null {
if (model === "FIRST_TOUCH") {
// Keep original attribution
return existingCookie ?? newRefCode;
} else {
// Override with latest
return newRefCode ?? existingCookie;
}
}7. Multi-Touch Attribution
Tracking Multiple Touchpoints:
CREATE TABLE mlm.attribution_touchpoints (
id UUID PRIMARY KEY,
anonymous_id VARCHAR(100), -- Before registration
user_id UUID, -- After registration
partner_id UUID NOT NULL,
link_id UUID,
touched_at TIMESTAMP NOT NULL,
touchpoint_type VARCHAR(20), -- CLICK, VISIT, IMPRESSION
utm_source VARCHAR(100),
utm_medium VARCHAR(100),
utm_campaign VARCHAR(100),
landing_page VARCHAR(500)
);Touchpoint Query (for reporting):
SELECT
ra.user_id,
COUNT(*) as touchpoint_count,
MIN(at.touched_at) as first_touch,
MAX(at.touched_at) as last_touch,
ARRAY_AGG(DISTINCT p.referral_code) as partner_codes
FROM mlm.attribution_touchpoints at
JOIN mlm.partners p ON p.id = at.partner_id
LEFT JOIN mlm.referral_attributions ra ON ra.user_id = at.user_id
GROUP BY ra.user_id;8. Edge Cases
Expired Cookies
Scenario: User clicks referral link, cookie expires, then registers.
Handling:
- No attribution created
- User is "organic"
- Can be manually attributed by admin if proof exists
Multiple Referrers
Scenario: User clicks Partner A's link, then Partner B's link.
First-Touch (Default):
- Partner A gets attribution
- Partner B's click is recorded but not attributed
Last-Touch (Alternative):
- Partner B gets attribution
- Partner A loses potential commission
Self-Referral Prevention
Rules:
- Partner cannot earn commission from their own purchases
- Partner cannot use their own referral link
Implementation:
function validateNotSelfReferral(userId: string, partnerCode: string): boolean {
const userPartner = await db.partner.findUnique({
where: { userId },
});
if (userPartner?.referralCode === partnerCode) {
return false; // Self-referral attempt
}
return true;
}Upline Referral Prevention
Rule: Cannot attribute to a downline partner (circular reference)
Implementation:
async function validateNotDownline(
newUserPartnerId: string,
referringPartnerId: string,
): Promise<boolean> {
// Check if referring partner is in new user's downline
const isDownline = await db.partnerTreePath.findFirst({
where: {
ancestorId: newUserPartnerId,
descendantId: referringPartnerId,
depth: { gt: 0 },
},
});
return !isDownline; // Valid if NOT in downline
}Cart Attribution Conflicts
Scenario: Guest cart has Partner A's attribution, user logs in with Partner B's attribution.
Resolution:
- If user has existing attribution, use that (permanent)
- Cart attribution is secondary
- Log conflict for review
9. Attribution Data Storage
Database Tables:
-- User's permanent attribution
CREATE TABLE mlm.referral_attributions (
id UUID PRIMARY KEY,
user_id UUID NOT NULL UNIQUE, -- One attribution per user
partner_id UUID NOT NULL,
link_id UUID,
attribution_type VARCHAR(20), -- FIRST_TOUCH, LAST_TOUCH
first_touch_at TIMESTAMP,
last_touch_at TIMESTAMP,
converted_at TIMESTAMP,
-- Metadata
cookie_id VARCHAR(100),
ip_address INET,
user_agent TEXT,
utm_source VARCHAR(100),
utm_medium VARCHAR(100),
utm_campaign VARCHAR(100)
);
-- Per-order attribution (for commission lookup)
-- Stored directly on orders table as referring_partner_idAttribution Reporting Endpoint: GET /partners/me/attribution/report
Response:
{
"period": {
"start": "2024-01-01",
"end": "2024-01-31"
},
"summary": {
"totalClicks": 1250,
"uniqueVisitors": 890,
"registrations": 85,
"conversions": 42,
"clickToRegistration": 6.8,
"registrationToConversion": 49.4
},
"byLink": [
{
"linkId": "uuid",
"linkName": "Instagram Bio",
"clicks": 500,
"registrations": 35,
"conversions": 18
}
],
"bySource": [
{
"source": "instagram",
"clicks": 750,
"registrations": 55,
"conversions": 28
}
],
"topCampaigns": [
{
"campaign": "winter_sale",
"clicks": 300,
"conversions": 15,
"revenue": 150000.0
}
]
}Error Scenarios
| Scenario | Behavior | User Impact |
|---|---|---|
| Invalid partner code | Silent fail, no cookie set | User proceeds normally |
| Partner inactive/suspended | Attribution not created | User registered without referral |
| Cookie blocked by browser | Attribution lost | No referral credit |
| Cookie expired | Attribution lost | No referral credit |
| Self-referral attempt | Attribution rejected | User registered without referral |
| Circular reference attempt | Attribution rejected | Error message shown |