Skip to content

Referral Attribution Flow

Complete flow for tracking, attributing, and crediting referrals to partners.

Overview

The referral attribution system tracks:

  1. Referral link clicks and cookie management
  2. Attribution capture on registration
  3. Attribution capture on purchases (including guest)
  4. Attribution window and rules
  5. Multi-touch attribution considerations
  6. Data storage and reporting

Main Flow Diagram


Step Details

Standard Link Format:

https://platform.com/{targetPath}?ref={partnerCode}&utm_source={source}&utm_medium={medium}&utm_campaign={campaign}

Components:

ComponentRequiredExampleDescription
refYesABC123XYPartner's referral code
utm_sourceNoinstagramTraffic source
utm_mediumNobioMarketing medium
utm_campaignNowinter_2024Campaign 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/ABC123XY

Cookies Set on Referral Click:

Cookie NameValueDurationHttpOnlySecure
iwm_ref_codePartner code30 daysNoYes
iwm_ref_link_idLink UUID30 daysNoYes
iwm_first_touchISO timestamp30 daysNoYes
iwm_last_touchISO timestampSessionNoYes
iwm_utmJSON (base64)SessionNoYes

Cookie Setting Logic:

typescript
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 param
  • iwm_last_touch: Updated on every visit (if cookie exists)

3. Click Tracking

Endpoint: POST /attribution/track-click

Request:

json
{
  "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:

sql
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:

  1. Read attribution cookies from request
  2. Validate partner code exists and is active
  3. Create user record
  4. Create referral_attribution linking user to partner
  5. Update denormalized counters

Attribution Record Created:

json
{
  "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:

sql
-- 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:

typescript
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:

typescript
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:

sql
-- Order stores the referring partner
CREATE TABLE product.orders (
    ...
    referring_partner_id UUID REFERENCES mlm.partners(id),
    ...
);

6. Attribution Window Rules

Default Configuration:

SettingValueDescription
Cookie Duration30 daysTime attribution cookie is valid
Attribution ModelFirst-touchFirst referrer gets credit
Click Window30 daysTime from click to conversion
Multi-purchaseLifetimeUser always attributed to original partner

First-Touch vs Last-Touch:

ModelDescriptionWhen Used
First-TouchFirst referrer gets full creditDefault for registrations
Last-TouchLast referrer gets full creditCan be configured
LinearSplit credit among all touchpointsEnterprise feature

Attribution Logic:

typescript
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:

sql
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):

sql
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:

  1. Partner cannot earn commission from their own purchases
  2. Partner cannot use their own referral link

Implementation:

typescript
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:

typescript
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:

  1. If user has existing attribution, use that (permanent)
  2. Cart attribution is secondary
  3. Log conflict for review

9. Attribution Data Storage

Database Tables:

sql
-- 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_id

Attribution Reporting Endpoint: GET /partners/me/attribution/report

Response:

json
{
  "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

ScenarioBehaviorUser Impact
Invalid partner codeSilent fail, no cookie setUser proceeds normally
Partner inactive/suspendedAttribution not createdUser registered without referral
Cookie blocked by browserAttribution lostNo referral credit
Cookie expiredAttribution lostNo referral credit
Self-referral attemptAttribution rejectedUser registered without referral
Circular reference attemptAttribution rejectedError message shown