Designing a Multi-Touch Affiliate & Referral Attribution System
How marketplaces like Amazon, Flipkart, Uber, and Airbnb track every click a referrer generates, follow a customer across days or weeks of browsing, and fairly decide who gets paid when a sale finally happens — explained plainly enough for a first-week engineer, and deep enough to walk into a senior systems interview with confidence.
Introduction & History
Imagine you run a small blog reviewing kitchen gadgets. You write a glowing review of a blender, put a special link at the bottom, and a reader clicks it, browses the marketplace for a week, gets distracted, comes back through a Google ad, then finally buys the blender ten days later. Should you get credit for that sale? Should the Google ad get credit? Should both? This exact question — “who caused this sale to happen” — is the entire reason affiliate and referral attribution systems exist.
Affiliate marketing is not new. It began in the mid-1990s when Amazon launched its “Associates Program” in 1996, letting website owners place links to Amazon products and earn a small commission on resulting sales. In those early days, attribution was almost embarrassingly simple: a cookie was dropped in the browser when someone clicked a link, and if that same browser bought something before the cookie expired, the click got 100% of the credit. This is called last-touch attribution, and for a single-referrer, single-device world, it worked fine.
But the internet changed. Customers now research a purchase across multiple devices — a phone during a commute, a laptop at work, a tablet at night. They encounter a brand through a YouTube influencer, then a coupon site, then a retargeting ad, then finally search the brand name directly and buy. A modern marketplace might have thousands of active referrers: bloggers, YouTubers, cashback apps, comparison sites, employee referral programs, and existing customers referring friends. The question “who gets the commission” became a genuinely hard distributed-systems and data-modeling problem, not just a cookie-dropping exercise.
This is why every serious marketplace today — Amazon, Flipkart, Myntra, Uber, Airbnb, Swiggy — runs a dedicated attribution platform: a backend system whose entire job is to capture every touchpoint a potential buyer has with a referral link, stitch those touchpoints together into one customer journey, and then apply a fair mathematical rule to decide how commission is split when a purchase eventually happens.
Think of a wedding that ten different people helped make happen: the friend who first introduced the couple, the family member who suggested a date, the caterer’s mutual friend who broke the ice, and the wedding planner who closed all the final details. If you had to divide a “thank you” gift fairly among all ten, you would not hand the entire gift to whoever happened to be standing next to the couple when they said “let’s do this.” You would think about who contributed early, who contributed at the critical moment, and how much each touchpoint mattered. Attribution systems do exactly this, mathematically, for every online sale.
We are going to design this system from scratch: how a click is captured, how a customer is recognized across multiple visits and devices, how the system decides which referrers deserve credit for a sale, how commissions are calculated and paid out, and how the whole thing is kept fast, fraud-resistant, and reliable at the scale of millions of clicks per day.
It is also worth understanding how privacy regulation has reshaped this space over the last decade, because it directly explains several architectural choices later in this tutorial. Regulations like the European Union’s GDPR and India’s Digital Personal Data Protection Act, 2023 impose real constraints on how long personal identifiers can be retained and how consent must be captured before tracking a visitor. At the same time, browser vendors independently began restricting third-party cookies well ahead of most regulation, forcing the entire industry toward first-party tracking domains, shorter default retention windows, and a much heavier reliance on explicit login events rather than passive fingerprinting for identity resolution. A modern attribution system, in other words, is not just an engineering problem — it operates inside a genuinely shifting legal and technical landscape, and durable designs build in configurability around retention windows and consent handling from day one rather than treating them as an afterthought.
Problem & Motivation
Let us define the problem precisely before we design anything, because “attribution” means different things to different people and vague requirements produce bad architectures.
2.1 What We Are Actually Building
A marketplace wants to run a program where external parties — bloggers, influencers, comparison-shopping sites, cashback apps, or even ordinary customers referring friends — can generate special trackable links. When someone clicks such a link and eventually buys something, the referrer should earn a commission. The system must:
- Generate and manage unique referral links and codes for thousands of referrers.
- Track every click on those links in real time, at high volume.
- Recognize the same visitor across multiple sessions, devices, and days (identity resolution).
- Record every subsequent “touchpoint” that visitor has with the marketplace, not just the first click.
- When a purchase happens, determine which touchpoint(s) deserve credit, using a configurable attribution model.
- Calculate the correct commission amount per referrer per order line item.
- Detect and block fraud — fake clicks, cookie stuffing, self-referrals, bot traffic.
- Give referrers a dashboard showing clicks, conversions, and earnings.
- Pay out earned commissions on a schedule, handling refunds and clawbacks correctly.
2.2 Why This Is Genuinely Hard
| Challenge | Why it is hard |
|---|---|
| Multi-touch journeys | A single order might have five or more touchpoints spread across weeks. The system must remember all of them, not just the last one, without unbounded storage growth. |
| Cross-device identity | The same human might click a link on their phone but buy on a laptop. Cookies alone cannot bridge that gap. |
| Scale | A large marketplace may see tens of millions of clicks a day and needs sub-100-millisecond redirect latency on every single one — a slow redirect is a lost sale. |
| Fraud | Money is directly at stake, so referrers have a financial incentive to game the system through fake clicks, cookie stuffing, or self-purchases. |
| Correctness of money | Commission calculations must be auditable and reversible — refunds must claw back commissions, and every payout must be traceable to specific orders. |
| Attribution ambiguity | There is no single “correct” answer to who caused a sale. The business must choose a model (first-touch, last-touch, linear, time-decay, position-based) and the system must support recomputing history if that model changes. |
2.3 Non-Functional Requirements
Beyond the functional list above, a handful of non-functional requirements shape almost every architectural decision made later in this tutorial, so it is worth stating them explicitly up front.
| Requirement | Target | Why it matters |
|---|---|---|
| Redirect latency | Sub-100ms at p99 | Slow redirects measurably increase checkout abandonment; this is the single most customer-visible number in the whole system. |
| Click durability | No silent data loss | A lost click can never be recovered — there is no secondary source of truth for “this person clicked this link at this moment.” |
| Commission correctness | Cent-accurate, fully auditable | Directly affects real money owed to real people and must survive financial audits and legal disputes. |
| Availability of click path | Very high, multi-region resilient | The click-redirect endpoint effectively is the referral program from the customer’s point of view; if it is down, referred sales simply do not happen. |
| Attribution recomputability | Fully deterministic and replayable | Needed for audits, model changes, and dispute resolution long after the original events occurred. |
A common first instinct is: “just store a cookie with the referrer ID, and whoever’s cookie is present at checkout gets the commission.” This is last-click attribution baked directly into the checkout code. It seems simple, but it silently punishes the blogger who did the actual persuasion work, rewards low-value “coupon code” sites that intercept buyers seconds before checkout, and gives the business zero flexibility to ever change how they think about attribution — because the logic is scattered across the checkout flow instead of centralized in a dedicated engine.
Core Concepts
3.1 Referral Link and Referral Code
A referral code is a short unique identifier tied to a referrer, for example REF-AJAY123. A referral link embeds that code into a URL, such as https://marketplace.com/product/blender-9000?ref=AJAY123. When a marketplace supports deep linking into a mobile app, the same code also needs to survive an app install — this uses techniques like deferred deep linking, where the code is temporarily stored server-side and matched to the app’s first open using device fingerprint signals.
Think of a referral code like a coat-check ticket. The ticket itself does not hold your coat — it just points to where the coat is stored. Similarly, ref=AJAY123 does not hold any commission data itself; it just points the system to Ajay’s referrer record, which lives safely in a database.
3.2 Click, Touchpoint, and Session
A click is the raw event: someone clicked a referral link at a specific timestamp, from a specific IP, device, and browser. A touchpoint is a slightly richer concept: any recorded interaction that could plausibly influence a future purchase — this includes clicks on referral links, but can also include opening a push notification, clicking an email campaign link, or engaging with a retargeting ad. A session groups touchpoints that happen close together in time from the same device into one browsing visit, typically closed after 30 minutes of inactivity.
3.3 Identity Resolution
Because the same person may use multiple devices, the system needs a way to link a phone-session and a laptop-session to one underlying “customer journey.” This is done using a combination of: a persistent first-party cookie, a probabilistic device fingerprint (screen size, browser version, timezone — used cautiously due to privacy regulation), and, most reliably, an actual login. Once someone logs in on any device, all prior anonymous sessions with a matching cookie or fingerprint can be merged into a single identity graph.
3.4 Attribution Window
An attribution window (also called a “cookie lifetime” or “lookback window”) is the maximum time between a touchpoint and a purchase for that touchpoint to still count. A typical e-commerce marketplace uses a 7 to 30 day window. If Ajay’s link was clicked 45 days before a purchase and the window is 30 days, that click is too old to receive credit.
3.5 Attribution Models
This is the mathematical heart of the system — the rule for splitting credit among multiple touchpoints in one customer journey.
| Model | Rule | Best for |
|---|---|---|
| First-touch | 100% of credit to the very first touchpoint in the journey. | Rewarding top-of-funnel discovery, like a blogger who introduces a brand. |
| Last-touch | 100% of credit to the touchpoint immediately before purchase. | Simple programs, or rewarding closers like cashback/coupon sites. |
| Linear | Credit split evenly across every touchpoint in the journey. | Programs that value every interaction equally. |
| Time-decay | Touchpoints closer to the purchase get exponentially more credit than earlier ones. | Balancing “closers” with some credit to earlier influence. |
| Position-based (U-shaped) | A fixed percentage, commonly 40%, to the first touchpoint, 40% to the last, and the remaining 20% split among the middle ones. | Valuing both discovery and the final push, a very common industry default. |
- “How would you design the data model so that switching attribution models does not require re-architecting the system?” — Answer: store the raw touchpoint history immutably, and treat the attribution model as a pure function applied at commission-calculation time, not baked into the write path.
- “What happens if the business changes the attribution model after commissions have already been paid?” — Discuss recomputation, versioning of attribution rules, and why you never mutate historical commission records, only issue adjustments.
3.6 Conversion and Commission
A conversion is the target action — usually a completed and paid order — that the referral program rewards. A commission is the money owed to a referrer, computed as a percentage of order value (or a flat amount per conversion), split across eligible touchpoints according to the attribution model, and adjusted for taxes, refunds, and program-specific caps.
3.7 Cookie Mechanics in Detail
It helps to understand exactly what a “referral cookie” actually contains, since a lot of attribution correctness comes down to getting this small piece of data right. A well-designed referral cookie is not just a raw referral code — it typically stores a JSON-like payload containing the anonymous visitor ID, the referral code that first (or most recently) touched this browser, a timestamp, and an expiry that matches the attribution window exactly. First-party cookies (set by the marketplace’s own domain) are far more reliable than third-party cookies, because most modern browsers — Safari’s Intelligent Tracking Prevention, Firefox’s Enhanced Tracking Protection, and Chrome’s ongoing phase-out of third-party cookies — aggressively block or shorten the lifespan of third-party tracking cookies. This is precisely why the Click Tracking Service in this design always redirects through the marketplace’s own domain rather than an external tracking domain: it keeps the cookie first-party and therefore durable enough to survive the full attribution window.
A first-party cookie set on mkt.com when a user clicks a link on mkt.com/r/... can reliably persist for the full 30-day attribution window. A third-party cookie set by a separate tracking domain like tracker.example.com embedded as a pixel on mkt.com might be wiped by the browser within 24 hours or blocked outright — silently breaking attribution for a huge share of Safari and Firefox users. This single architectural decision — first-party versus third-party cookie domains — has a bigger real-world impact on attribution accuracy than almost any algorithm choice.
3.8 UTM Parameters and Campaign Metadata
Beyond the referral code itself, marketplaces typically also capture UTM parameters (utm_source, utm_medium, utm_campaign) on every click. These do not usually affect who gets paid, but they let the Reporting and Analytics Service break down performance by campaign — for example, showing a referrer that their Instagram-story links convert twice as well as their blog-post links, even though both use the same underlying referral code and both are eligible for the same commission.
3.9 Attribution Window Edge Cases
The attribution window sounds simple — “30 days from click to purchase” — but real systems have to handle several edge cases carefully:
- Renewing subscriptions: if the marketplace sells subscriptions, does a referral get credit only for the first payment, or for every renewal? Most programs credit only the first conversion, to avoid indefinitely paying a referrer for a customer relationship the marketplace itself now owns.
- Window extension on repeat clicks: if a customer clicks the same referral link twice, five days apart, does the window restart from the second click? Most systems treat this as extending, not restarting, the window, using the earliest qualifying click for first-touch credit while still counting the later click as a separate touchpoint for models like linear or position-based.
- Cross-program conflicts: what if a customer is inside the attribution window for two different, unrelated referral programs (say, a general affiliate link and a friend-referral code) at the same time? Program rules typically define priority order or allow both programs to earn independently, since they are drawing from different budgets.
3.10 Deep Linking Into Mobile Apps
Attribution gets meaningfully harder when the destination is a mobile app rather than a web page, because a click on a referral link often happens in a mobile browser, but the actual purchase happens after the user installs and opens a completely separate native app — and a native app has no access to the browser’s cookies. The standard solution is deferred deep linking: when the Click Tracking Service detects that the destination requires the app and the app is not yet installed, it stores the click’s visitor ID and referral code server-side, redirects to the app store, and then, on first app open, the app calls a matching endpoint with device signals (install time, device model, IP, timezone). The server performs a best-effort probabilistic match against recent unmatched clicks and, if confident enough, stitches the pre-install click into the user’s post-install journey.
Architecture & Components
Here is the full system, drawn as a set of layered components. Every box below explicitly shows where API Gateway and Load Balancer sit relative to it, since these two components sit in front of nearly every service call in this system.
4.1 Component Responsibilities
CDN
Caches static dashboard assets and, importantly, can serve extremely fast redirects for referral links at the edge, closer to the user, before the request even reaches the origin Load Balancer. For very high-traffic referral links — say, a promoted link during a flash sale — the CDN can hold a short-lived edge redirect rule so millions of clicks never touch origin infrastructure at all. The trade-off is that edge-cached redirects cannot carry the full click-logging logic, so systems typically use a “log-then-cache” pattern: the first click for a given code hits origin and populates the edge cache, and every following click within the cache TTL gets both the fast edge redirect and an asynchronous beacon-based click log.
Load Balancer
Distributes incoming traffic across many instances of the API Gateway and, indirectly, every downstream service. Performs health checks and removes unhealthy nodes automatically, typically using a mix of TCP-level liveness checks and HTTP-level readiness checks that verify a service can actually reach its own dependencies before being sent live traffic. Every service in this architecture sits behind it, and it is usually configured with connection draining so in-flight requests complete cleanly during a deploy or scale-down event rather than being abruptly terminated.
API Gateway
The single entry point for all client and referrer-dashboard traffic. Handles authentication (referrer API keys, dashboard JWTs), rate limiting per referrer, request routing to the correct microservice, and basic request validation before anything touches business logic. It also centralizes cross-cutting concerns like request logging, CORS policy, and response compression, so individual microservices do not each need to reimplement them. Because it sits in front of every service, the Gateway is also a natural place to enforce a global circuit breaker that protects the whole platform if one downstream service starts failing broadly.
Referral Link Management
Lets referrers create, view, and deactivate referral links and codes. Owns the mapping from short code to destination product or landing page, and enforces business rules like preventing a referrer from generating links to products outside their approved category, or capping the number of active links per account to reduce abuse surface. Sits behind the Load Balancer and API Gateway like every other service here, and publishes a cache-invalidation event to Redis whenever a link is edited or deactivated so the Click Tracking Service never serves a stale mapping.
Click Tracking Service
The hot path. Receives every click on a referral link, resolves the short code, logs the raw click event to Kafka, sets or reads a first-party cookie, and issues an HTTP redirect to the real destination — all within a tight latency budget, behind the Load Balancer and API Gateway. It is intentionally kept as thin and stateless as possible: no business logic beyond cookie handling and event publishing lives here, because every extra millisecond of processing on this path is multiplied across tens of millions of daily requests.
Identity Resolution
Consumes click and session events from Kafka and maintains an identity graph, merging anonymous cookie-based identities with logged-in user identities so a journey can be reconstructed across devices. It uses a union-find style data structure internally: every new anonymous ID starts as its own node, and a login event that matches a known cookie merges that node into the logged-in user’s cluster, so a lookup for “give me this customer’s full journey” can efficiently walk the merged graph instead of scanning every session ever recorded.
Fraud Detection
Scores clicks and conversions in near-real-time for suspicious patterns — click farms, cookie stuffing, self-referral — and can quarantine touchpoints before they ever influence a commission payout. It combines rule-based checks (velocity limits, IP reputation lists, known data-center IP ranges that real consumers rarely browse from) with a lightweight statistical model trained on historical confirmed-fraud cases, and every score it produces is logged so a human reviewer can later audit why a specific click or conversion was flagged.
Attribution Engine
The mathematical core. On every conversion event, retrieves the full touchpoint history for that customer journey within the attribution window and applies the configured attribution model to split credit. It is designed as a pure function over its inputs — given the same journey and the same model, it always produces the same split — which is what makes it safe to re-run for reconciliation, audits, or backfills after a bug fix.
Commission Calculation
Turns attributed credit into actual currency amounts, applying referrer-specific commission rates, program rules, tax handling, and caps, then writes an auditable commission ledger entry. It also enforces program-level guardrails, such as a maximum commission per order or a minimum order value threshold below which no commission is paid, both of which protect the marketplace’s margin on very low-value transactions.
Payout Service
Batches approved commissions on a schedule (e.g. monthly), integrates with payment rails, and handles retries, holds, and clawbacks for refunded orders. It maintains its own state machine per payout batch — pending, processing, completed, failed, retried — so operators always have clear visibility into exactly where a specific referrer’s money currently sits in the pipeline.
Reporting & Analytics
Powers the referrer dashboard and internal marketplace analytics — clicks, conversion rate, top referrers — reading from the data warehouse rather than the live transactional stores to avoid impacting production traffic. It typically pre-aggregates common queries (daily clicks per referrer, monthly commission totals) into materialized summary tables so dashboard loads stay fast even as the underlying raw event volume grows into the billions of rows.
Internal Working
5.1 Anatomy of a Single Click
When a customer clicks https://mkt.com/r/blender9000?ref=AJAY123, here is exactly what happens, step by step, all behind the Load Balancer and API Gateway:
- DNS resolves to the CDN edge node closest to the user.
- The CDN checks if it has a cached redirect rule for this exact short link; if the destination rarely changes, it can redirect immediately without contacting the origin — shaving tens of milliseconds off latency.
- If not cached, the request passes through the Load Balancer to the API Gateway.
- The API Gateway performs lightweight validation (is this a well-formed referral code, is the referrer’s account active) and routes to the Click Tracking Service.
- The Click Tracking Service looks up the referral code in Redis (hot cache) or PostgreSQL (cold fallback) to find the destination URL and referrer ID.
- It checks for an existing first-party cookie identifying this browser; if none exists, it generates a new anonymous visitor ID and sets a cookie with an expiry matching the attribution window.
- It publishes a click event to Kafka containing: visitor ID, referrer ID, referral code, timestamp, product ID, IP (hashed for privacy), user agent, and any UTM parameters.
- It responds with an HTTP 302 redirect straight to the product page.
All of this — cache lookup, cookie logic, event publish, redirect — must happen inside a tight latency budget, typically under 100 milliseconds, because every extra millisecond of redirect delay measurably increases the chance the user abandons before the destination page even loads.
5.2 Sequence: Multi-Touch Journey to Conversion
5.3 Java: Click Event Ingestion
The Click Tracking Service publishes a well-structured event rather than raw HTTP parameters, so every downstream consumer works with a stable contract.
@RestController
@RequestMapping("/r")
public class ClickRedirectController {
private final ReferralLinkCache linkCache;
private final KafkaTemplate<String, ClickEvent> kafkaTemplate;
private final CookieService cookieService;
@GetMapping("/{shortCode}")
public ResponseEntity<Void> handleClick(
@PathVariable String shortCode,
HttpServletRequest request,
HttpServletResponse response) {
ReferralLink link = linkCache.resolve(shortCode);
if (link == null || !link.isActive()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
String visitorId = cookieService.getOrCreateVisitorId(
request, response, link.getAttributionWindowDays());
ClickEvent event = ClickEvent.builder()
.visitorId(visitorId)
.referrerId(link.getReferrerId())
.referralCode(shortCode)
.productId(link.getProductId())
.timestamp(Instant.now())
.ipHash(IpHasher.hash(request.getRemoteAddr()))
.userAgent(request.getHeader("User-Agent"))
.build();
// Fire-and-forget publish keeps redirect latency low.
kafkaTemplate.send("click-events", visitorId, event);
response.setHeader("Location", link.getDestinationUrl());
return ResponseEntity.status(HttpStatus.FOUND).build();
}
}5.4 Java: Attribution Engine Core Logic
public class AttributionEngine {
public List<AttributedCredit> attribute(
List<Touchpoint> journey,
AttributionModel model,
BigDecimal orderValue) {
// Only touchpoints inside the attribution window are eligible.
List<Touchpoint> eligible = journey.stream()
.filter(Touchpoint::isWithinAttributionWindow)
.sorted(Comparator.comparing(Touchpoint::getTimestamp))
.collect(Collectors.toList());
if (eligible.isEmpty()) {
return Collections.emptyList();
}
switch (model) {
case FIRST_TOUCH:
return List.of(fullCredit(eligible.get(0)));
case LAST_TOUCH:
return List.of(fullCredit(eligible.get(eligible.size() - 1)));
case LINEAR:
BigDecimal equalShare = BigDecimal.ONE
.divide(BigDecimal.valueOf(eligible.size()), 6, RoundingMode.HALF_UP);
return eligible.stream()
.map(tp -> new AttributedCredit(tp, equalShare))
.collect(Collectors.toList());
case POSITION_BASED:
return applyPositionBased(eligible);
case TIME_DECAY:
return applyTimeDecay(eligible, orderValue);
default:
throw new IllegalArgumentException("Unsupported model: " + model);
}
}
private List<AttributedCredit> applyPositionBased(List<Touchpoint> eligible) {
if (eligible.size() == 1) {
return List.of(fullCredit(eligible.get(0)));
}
List<AttributedCredit> result = new ArrayList<>();
BigDecimal firstShare = BigDecimal.valueOf(0.40);
BigDecimal lastShare = BigDecimal.valueOf(0.40);
int middleCount = eligible.size() - 2;
BigDecimal middleShare = middleCount == 0
? BigDecimal.ZERO
: BigDecimal.valueOf(0.20).divide(BigDecimal.valueOf(middleCount), 6, RoundingMode.HALF_UP);
for (int i = 0; i < eligible.size(); i++) {
Touchpoint tp = eligible.get(i);
if (i == 0) {
result.add(new AttributedCredit(tp, firstShare));
} else if (i == eligible.size() - 1) {
result.add(new AttributedCredit(tp, lastShare));
} else {
result.add(new AttributedCredit(tp, middleShare));
}
}
return result;
}
private AttributedCredit fullCredit(Touchpoint tp) {
return new AttributedCredit(tp, BigDecimal.ONE);
}
}“Why use BigDecimal instead of double for commission math?” — Because floating-point arithmetic introduces rounding errors that compound over millions of transactions, and money must reconcile to the cent. Always discuss fixed-point or BigDecimal arithmetic for anything financial.
5.5 Key Algorithms and Data Structures
A few classic data structures and algorithms show up repeatedly once you start building this system for real, and interviewers often probe on exactly these choices.
5.5.1 Sliding Window Rate Limiting
To stop click-fraud bots from hammering a single referral link thousands of times a second, the Click Tracking Service applies a sliding-window rate limiter per IP-and-referral-code pair, backed by Redis sorted sets. Each click adds a timestamped entry; before accepting a click, the service trims entries older than the window and checks whether the remaining count exceeds the threshold.
public class SlidingWindowRateLimiter {
private final RedisTemplate<String, String> redis;
private final int maxRequests;
private final Duration window;
public boolean allow(String key) {
long now = System.currentTimeMillis();
long windowStart = now - window.toMillis();
String redisKey = "rl:" + key;
redis.opsForZSet().removeRangeByScore(redisKey, 0, windowStart);
Long count = redis.opsForZSet().zCard(redisKey);
if (count != null && count >= maxRequests) {
return false;
}
redis.opsForZSet().add(redisKey, UUID.randomUUID().toString(), now);
redis.expire(redisKey, window);
return true;
}
}5.5.2 Union-Find for Identity Graph Merging
The Identity Resolution Service’s core job — merging anonymous sessions into one journey once a login event bridges them — maps naturally onto the classic union-find (disjoint-set) data structure, giving near-constant-time merges and lookups even as the identity graph grows to hundreds of millions of nodes.
public class IdentityUnionFind {
private final Map<String, String> parent = new ConcurrentHashMap<>();
public String find(String id) {
String root = id;
while (!parent.getOrDefault(root, root).equals(root)) {
root = parent.get(root);
}
// Path compression for future lookups.
parent.put(id, root);
return root;
}
public void union(String idA, String idB) {
String rootA = find(idA);
String rootB = find(idB);
if (!rootA.equals(rootB)) {
parent.put(rootB, rootA);
}
}
}In production this in-memory sketch is backed by a persistent store (Cassandra or a graph database) partitioned so that lookups for a given visitor’s canonical identity stay fast even at scale, with an in-memory LRU cache in front for the hottest recently-active visitors.
5.5.3 Bloom Filter for Duplicate Click Suppression
A single accidental double-tap or a retried HTTP request can generate two identical click events. Rather than paying for an exact-match database lookup on every single click just to catch this rare case, the Click Tracking Service keeps a probabilistic Bloom filter of recently seen click fingerprints (visitor ID plus referral code plus a coarse timestamp bucket) in memory. A Bloom filter can never produce a false negative, so if it says “not seen before,” the click is definitely new; a small, tunable false-positive rate is an acceptable trade-off for avoiding a database round-trip on every request.
5.5.4 Consistent Hashing for Kafka Partitioning
Click and touchpoint events are partitioned in Kafka by visitor ID using consistent hashing, which guarantees that every event for the same visitor lands on the same partition and is therefore processed in strict order by the same consumer instance — essential for the Identity Resolution Service and Attribution Engine, both of which need to see a visitor’s events in chronological order to build a correct journey.
5.6 Java: Commission Calculation Service
Once the Attribution Engine has split credit across touchpoints, the Commission Service converts those shares into real currency, applies referrer-specific rates and program guardrails, and writes an idempotent ledger entry.
@Service
public class CommissionCalculationService {
private final ReferrerRateRepository rateRepository;
private final CommissionLedgerRepository ledgerRepository;
@Transactional
public void calculateAndRecord(String orderId, BigDecimal orderValue,
List<AttributedCredit> credits) {
// Idempotency guard: never double-write for the same order.
if (ledgerRepository.existsByOrderId(orderId)) {
return;
}
for (AttributedCredit credit : credits) {
ReferrerRate rate = rateRepository.findActiveRate(
credit.getTouchpoint().getReferrerId());
BigDecimal eligibleValue = orderValue.multiply(credit.getShare());
BigDecimal commission = eligibleValue
.multiply(rate.getCommissionPercent())
.setScale(2, RoundingMode.HALF_UP);
commission = applyProgramCap(commission, rate);
CommissionLedgerEntry entry = CommissionLedgerEntry.builder()
.orderId(orderId)
.referrerId(credit.getTouchpoint().getReferrerId())
.attributionShare(credit.getShare())
.commissionAmount(commission)
.status(CommissionStatus.PENDING_APPROVAL)
.createdAt(Instant.now())
.build();
ledgerRepository.save(entry);
}
}
private BigDecimal applyProgramCap(BigDecimal commission, ReferrerRate rate) {
if (rate.getMaxCommissionPerOrder() != null
&& commission.compareTo(rate.getMaxCommissionPerOrder()) > 0) {
return rate.getMaxCommissionPerOrder();
}
return commission;
}
}5.7 Java: Fraud Scoring on the Click Path
Fraud scoring must stay asynchronous relative to the redirect, so it runs as a Kafka consumer rather than an inline call, producing a score that later gates whether a touchpoint is eligible for attribution.
@KafkaListener(topics = "click-events", groupId = "fraud-detection")
public class FraudScoringConsumer {
private final IpReputationClient ipReputation;
private final ClickVelocityStore velocityStore;
private final KafkaTemplate<String, FraudScoreEvent> scoreTopic;
public void onClick(ClickEvent event) {
int score = 0;
if (ipReputation.isDataCenterIp(event.getIpHash())) {
score += 40;
}
int recentClicksFromIp = velocityStore.countInWindow(
event.getIpHash(), Duration.ofMinutes(1));
if (recentClicksFromIp > 20) {
score += 35;
}
if (velocityStore.isKnownSelfReferral(event.getVisitorId(), event.getReferrerId())) {
score += 50;
}
FraudScoreEvent scored = new FraudScoreEvent(
event.getVisitorId(), event.getReferralCode(), score, Instant.now());
scoreTopic.send("fraud-scores", event.getVisitorId(), scored);
velocityStore.recordClick(event.getIpHash());
}
}A configurable threshold (commonly 70 or higher on a 0-100 scale) determines whether a touchpoint is automatically excluded from attribution eligibility, routed to manual review, or allowed through — giving the business a tunable dial between aggressive fraud prevention and avoiding false positives that wrongly deny genuine referrers their earned commission.
Data Flow & Lifecycle
Let us trace one referral-driven order from click to payout, showing exactly which store owns each piece of data.
6.1 Lifecycle Stages Explained
- Capture: Click Tracking Service writes the raw event to Kafka within milliseconds of the click.
- Persistence: A Kafka consumer writes the event into Cassandra, chosen for its write-heavy, append-only workload characteristics and natural time-series partitioning by visitor ID.
- Identity Merge: The Identity Resolution Service periodically or reactively merges sessions belonging to the same underlying person, updating a lightweight journey summary cached in Redis for fast retrieval at conversion time.
- Conversion Trigger: When an order is marked paid (not just placed — refund risk means marketplaces usually wait for payment confirmation), the Order Service emits an
order-confirmedevent. - Attribution: The Attribution Engine consumes that event, pulls the visitor’s full touchpoint history from Cassandra (using the Redis-cached journey as a fast path), filters to the attribution window, and applies the configured model.
- Commission Calculation: Attributed credit shares are converted into actual currency, referrer rates applied, and a ledger entry written to PostgreSQL — the system of record for money.
- Payout: On a schedule, the Payout Service aggregates approved, non-refund-risk commissions per referrer and initiates a transfer.
- Refund Handling: If an order is later refunded, a compensating
order-refundedevent flows through the same pipeline, generating a negative commission ledger entry (a clawback) rather than deleting the original record — preserving a full audit trail.
Every event in this system — clicks, touchpoints, commission ledger entries — is append-only. Nothing is ever updated in place. A refund does not erase a commission; it adds an offsetting entry. This is not a stylistic choice — it is a legal and accounting requirement. Auditors and referrers must be able to reconstruct exactly why they were paid a specific amount on a specific date.
6.2 Handling Real-World Edge Cases
A handful of scenarios come up constantly in production and are worth designing for explicitly rather than discovering as incidents later.
- Multi-item orders with mixed eligibility: A single order might contain one product that was reached via a referral link and another that was not. The Attribution Engine operates at the order-line-item level, not the whole-order level, so only the eligible line items generate commission, and the ledger entry references the specific line item, not just the order as a whole.
- Order cancelled before payment confirms: Because attribution only triggers on an
order-confirmed(paid) event rather than anorder-placedevent, an order that is abandoned or cancelled at the payment step never enters the attribution pipeline at all, avoiding the need to generate and then reverse a commission that was never really earned. - Partial refunds: If only part of an order is refunded, the compensating clawback event carries the exact refunded line-item amount, and the Commission Service recalculates a proportional negative adjustment rather than clawing back the full original commission.
- Referrer account suspended after the click but before conversion: The touchpoint itself remains a valid historical record, but the Commission Service checks referrer account status at calculation time, and a suspended referrer’s otherwise-eligible commission is held in a pending-review state rather than either being silently paid or silently discarded.
Advantages, Disadvantages & Trade-offs
Dedicated attribution platform
- Attribution logic is centralized and can evolve without touching checkout code.
- Supports sophisticated multi-touch models, which are fairer to referrers and encourage higher-quality referral traffic, not just last-second coupon interception.
- Full audit trail supports finance, tax, and dispute resolution.
- Fraud detection can be layered in without disrupting the tracking pipeline.
Costs of this approach
- Significant infrastructure complexity compared to a simple last-click cookie.
- Multi-touch storage and identity resolution add real operational cost at scale.
- Cross-device identity resolution is inherently probabilistic and never 100% accurate.
- Recomputing historical attribution after a model change is expensive and must be handled carefully to avoid double-paying commissions.
7.1 Key Trade-off: Attribution Window Length
A longer attribution window (say, 30 days) is more generous to referrers and captures more of the true buying journey, but increases storage retention requirements and makes fraud detection harder because more time passes between click and conversion. A shorter window (say, 3 days) is cheaper and simpler but may unfairly deny credit to a blogger whose review genuinely convinced someone who took two weeks to actually buy.
7.2 Key Trade-off: Real-Time vs Batch Attribution
Real-time attribution (running the Attribution Engine synchronously the moment an order is confirmed) gives referrers instant dashboard visibility but requires the journey data to always be fast to query. Batch attribution (running a nightly job over all of that day’s orders) is operationally simpler and cheaper, but referrers see a delay before knowing they earned a commission. Most large-scale systems use a hybrid: a fast, approximate real-time attribution for dashboard display, and an authoritative nightly batch recomputation for the actual commission ledger.
7.3 Key Trade-off: Probabilistic vs Deterministic Identity Resolution
Relying only on deterministic identity signals — first-party cookies and explicit logins — is precise and privacy-friendly, but understates true cross-device journeys, since a large share of customers never log in until the moment they actually check out. Adding probabilistic signals — device fingerprinting, IP and timezone matching — recovers more of the true journey and generally increases attributed credit for early-funnel referrers, but introduces false-positive risk (incorrectly merging two different people’s sessions) and raises privacy and regulatory considerations that must be weighed against the attribution accuracy gained.
Performance & Scalability
The Click Tracking Service is the single hottest path in this entire architecture, because every click must redirect fast or the marketplace loses the sale regardless of who gets credit for it.
8.1 Scaling the Click Path
- Horizontal scaling: The Click Tracking Service is stateless — all state lives in Redis and Kafka — so it scales horizontally behind the Load Balancer with simple autoscaling rules based on request rate.
- Caching referral link lookups: Referral code to destination mappings rarely change, so they are cached in Redis with a long TTL and refreshed via a cache-invalidation event when a referrer updates a link.
- Asynchronous event publishing: The redirect response never waits for Kafka to confirm the write to disk; it uses a fire-and-forget or fast-acknowledge publish so redirect latency is not coupled to storage durability.
- Edge redirects: Where possible, redirect rules are pushed to CDN edge nodes so the busiest links never even hit the origin infrastructure.
8.2 Scaling the Attribution Engine
Unlike the click path, the Attribution Engine is not latency-critical for the customer — it runs after the order is already placed. This gives room to use Kafka consumer groups to parallelize processing across partitions (partitioned by visitor ID, which also conveniently keeps a single journey’s events in order), and to batch-fetch touchpoint history from Cassandra using efficient partition-key range queries instead of scattered point lookups.
8.3 Capacity Planning Example
| Metric | Example scale |
|---|---|
| Peak clicks per second | 5,000 – 20,000 during flash sales |
| Redirect latency target (p99) | Under 100 ms |
| Orders processed per day | 500,000 – 2,000,000 |
| Average touchpoints per converting journey | 2 – 6 |
| Attribution window | 7 – 30 days, configurable per program |
It is worth walking through how these numbers translate into actual infrastructure sizing, since this is exactly the kind of estimation an interviewer will often ask for. At a peak of 20,000 clicks per second, and assuming each Click Tracking Service instance can comfortably sustain around 2,000 requests per second while staying under the 100ms p99 latency budget, the fleet needs roughly ten to fifteen active instances at peak, with autoscaling policies keeping a smaller baseline fleet running during off-peak hours and scaling out ahead of known high-traffic events like sales. On the storage side, if each click event is roughly 500 bytes once serialized, 20,000 clicks per second works out to about 10 megabytes per second of raw ingest at peak, or a little over 800 gigabytes per day if that peak were sustained continuously — in practice traffic is far spikier than that, so real daily volume is usually a fraction of the theoretical peak-sustained figure, but sizing Kafka partition counts and broker throughput against the peak number, not the average, is what keeps the system from falling behind during exactly the moments — flash sales — when referral traffic and revenue both matter most.
“Where is the bottleneck likely to appear first as this system scales to 10x traffic?” — A strong answer identifies the Click Tracking Service’s dependency on Redis for referral link lookups, and discusses read replicas, local in-memory caching with short TTLs, and Kafka partition count as the levers to pull before the database layer becomes the constraint.
8.4 CAP Theorem and Consistency Choices
The CAP theorem — that a distributed system can only fully guarantee two of Consistency, Availability, and Partition tolerance at once — plays out concretely across this architecture, and different components deliberately land in different places on that spectrum.
| Component | Choice | Reasoning |
|---|---|---|
| Click Tracking + Redis link cache | Favors Availability over strict Consistency (AP) | Serving a slightly stale referral link mapping for a few seconds after an edit is far less costly than failing the redirect entirely. |
| Commission Ledger in PostgreSQL | Favors Consistency over Availability (CP) | Money must never be double-counted or lost; a brief unavailability during a partition is preferable to writing an incorrect commission. |
| Cassandra click/touchpoint store | Tunable, typically AP with eventual consistency | Individual click writes can tolerate eventual consistency across replicas since the Attribution Engine reads a full history window, not a single latest-value read. |
This mixed approach — sometimes called “polyglot consistency” — is a deliberate design choice: it would be wasteful to force the entire system into the strictest consistency model just because one component (money) genuinely needs it.
8.5 Concurrency Considerations
Two concurrency hazards deserve special attention in this system. First, two nearly-simultaneous orders from the same visitor (rare, but possible with duplicate browser tabs) could race to read the same touchpoint history and both trigger commission calculation; this is prevented by making the Commission Service’s write to PostgreSQL use an idempotency key derived from the order ID, so a duplicate calculation simply upserts the same row rather than creating two ledger entries. Second, the Kafka consumer groups for the Attribution Engine must guarantee in-order processing per visitor, which is why partitioning by visitor ID (not round-robin) is a hard architectural requirement, not just an optimization — processing a purchase event before its preceding click events would corrupt the journey.
High Availability & Reliability
A dropped click means lost commission-attribution data forever — there is no way to retroactively recover “someone clicked this link at this moment” if the event is lost. Reliability here is about never losing that signal, even if downstream processing is temporarily delayed.
- Multi-AZ deployment: The Click Tracking Service, Load Balancer, and Kafka brokers are deployed across multiple availability zones so a single data center failure does not take down link tracking.
- Kafka replication: Click event topics are replicated across at least three brokers with
acks=allfor the small subset of critical writes (like order-confirmed events), while high-volume click events can use a slightly relaxed acknowledgment setting favoring throughput, since occasional click loss is tolerable but order-event loss is not. - Graceful degradation: If Redis is briefly unavailable, the Click Tracking Service falls back to a direct (slower) PostgreSQL read for referral link resolution rather than failing the redirect entirely.
- Idempotent consumers: Every downstream consumer (Identity Resolution, Attribution Engine, Commission Service) is built to safely process a duplicate event without double-crediting a commission, using deterministic event IDs and upsert semantics.
- Circuit breakers: Calls from the Attribution Engine to the Commission Service use a circuit breaker so a slow or failing Commission Service does not cascade backpressure into the Kafka consumer group and stall the entire pipeline.
Large marketplaces design their click-redirect path to survive even a full regional outage of their attribution backend. If Kafka or the event pipeline is degraded, the redirect itself still succeeds instantly — the click is logged to a local durable buffer and replayed once the pipeline recovers. Losing attribution credit temporarily is acceptable; losing the sale itself is not.
9.1 Disaster Recovery and Backup Strategy
Because the PostgreSQL commission ledger is the financial system of record, it follows a strict backup regime: continuous write-ahead-log shipping to durable object storage, point-in-time recovery capability going back at least 35 days, and a warm standby replica in a second region that can be promoted within minutes if the primary region fails entirely. Cassandra’s own multi-node replication provides much of its own resilience, but nightly snapshots are still taken and shipped off-cluster as a safeguard against a correlated failure or an operational mistake like an accidental mass delete.
Recovery objectives are set explicitly per data store, since they do not all carry the same business risk: the commission ledger targets a Recovery Point Objective (RPO) of near zero — essentially no acceptable data loss — and a Recovery Time Objective (RTO) of minutes, while the click-event store can tolerate a slightly looser RPO of a few minutes, since even losing a short window of raw click data is recoverable in the aggregate business sense, whereas losing committed financial records is not.
9.2 Consensus and Replication in Kafka
Kafka’s own reliability rests on a consensus mechanism among broker replicas: each partition has one leader and several follower replicas, and a write is only considered durable once it has been replicated to the configured minimum number of in-sync replicas. For the order-confirmed topic — the trigger for real money movement — this system configures acks=all and a minimum of two in-sync replicas, trading a small amount of write latency for a strong guarantee that a confirmed order event cannot silently vanish if a single broker fails moments after accepting it.
9.3 Java: Circuit Breaker Around the Commission Service Call
Here is a concrete illustration of the circuit breaker mentioned earlier, protecting the Attribution Engine’s call into the Commission Service so a slow or failing downstream dependency degrades gracefully instead of stalling the whole Kafka consumer group.
@Service
public class CommissionServiceClient {
private final CircuitBreaker circuitBreaker;
private final RestTemplate restTemplate;
public CommissionServiceClient(CircuitBreakerRegistry registry, RestTemplate restTemplate) {
this.circuitBreaker = registry.circuitBreaker("commission-service",
CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.slidingWindowSize(20)
.build());
this.restTemplate = restTemplate;
}
public void sendAttributedCredits(String orderId, List<AttributedCredit> credits) {
Supplier<Void> call = CircuitBreaker.decorateSupplier(circuitBreaker, () -> {
restTemplate.postForObject(
"/internal/commissions/calculate",
new CommissionRequest(orderId, credits), Void.class);
return null;
});
try {
call.get();
} catch (CallNotPermittedException ex) {
// Circuit is open; park the event for retry instead of blocking the consumer.
deadLetterQueue.park(orderId, credits);
}
}
}When the circuit trips open after repeated failures, the Attribution Engine stops sending calls to a clearly unhealthy Commission Service and instead parks the affected events in a dead-letter queue for automatic retry once the circuit closes again, rather than letting a growing backlog of failing calls stall the Kafka consumer group and, with it, every other order waiting behind it in the partition.
Security
Because real money flows out of this system, it is a direct target for fraud, and security here is as much about financial integrity as it is about traditional data protection.
10.1 Common Attack Patterns
| Attack | Description | Mitigation |
|---|---|---|
| Click fraud / click farms | Bots or paid workers generate massive fake click volume to inflate a referrer’s apparent reach or trigger volume-based bonuses. | Rate limiting per IP and device fingerprint at the API Gateway; bot-detection scoring in the Fraud Detection Service; requiring a minimum session engagement before a click counts toward volume bonuses. |
| Cookie stuffing | A malicious referrer silently drops referral cookies on visitors who never actually saw or clicked their link, hijacking credit for organic sales. | Requiring an explicit, logged click event (not just cookie presence) as the source of truth; validating referrer header and click provenance server-side. |
| Self-referral | A referrer buys their own products, or has friends buy, purely to earn commission on money that was going to be spent anyway. | Matching billing/account identity against referrer identity; flagging suspiciously repetitive buyer-referrer pairs for manual review. |
| Last-click hijacking | A malicious browser extension or coupon-site script fires a hidden click on its own referral link right before checkout, stealing credit from the genuine influencer. | Server-side click validation with referrer-header and timing checks; giving legitimate first-touch referrers partial credit under non-last-touch models reduces the financial incentive for this attack. |
| Fake account rings | A coordinated group creates many fake customer accounts specifically to generate referral-eligible orders that are later cancelled or refunded after the commission window closes, extracting value without ever intending real purchases. | Correlating account creation velocity, shared payment instruments, and shared device signals across accounts; delaying payout eligibility until well past the typical refund window for a given product category. |
None of these mitigations work perfectly in isolation, which is exactly why the Fraud Detection Service is designed as a layered, scoring-based system rather than a single hard rule. A referrer flagged by one signal alone — say, a slightly elevated click-to-conversion ratio — might simply be running an unusually effective campaign; it is the combination of multiple independent signals crossing a combined threshold that meaningfully separates genuine high performers from coordinated abuse, and even then, the system defaults to routing borderline cases to human review rather than making an irreversible automated decision to withhold a real referrer’s earned commission.
10.2 Data Protection
- IP addresses are hashed before storage rather than kept in plaintext, satisfying data minimization principles under regulations like GDPR and India’s DPDP Act 2023.
- All traffic terminates TLS at the Load Balancer; internal service-to-service traffic uses mutual TLS within the private network.
- The API Gateway enforces authenticated, scoped API keys for referrer-facing endpoints, so one referrer can never query another referrer’s click or commission data.
- Payout Service integrates with payment providers using tokenized bank details, never storing raw account numbers in application databases.
“How would you detect cookie stuffing without blocking legitimate marketing tools?” — A good answer discusses requiring a genuine, timestamped, server-logged click event as the only valid source of attribution credit, cross-referencing referrer headers, and applying anomaly detection on click-to-conversion ratios that are implausibly high for organic behavior.
10.3 Authentication and Authorization Model
Referrers authenticate to their dashboard and API using scoped API keys, issued per referrer account and rotatable on demand if a key is suspected to be compromised. The API Gateway validates these keys on every request and attaches the resolved referrer identity to the request context, so every downstream service can enforce row-level authorization — a referrer’s dashboard query can only ever return that referrer’s own clicks and commissions, enforced both at the API layer and, defensively, again at the database query layer as a second line of defense. Internal service-to-service calls, such as the Attribution Engine calling the Commission Service, use short-lived service tokens issued by an internal identity provider rather than long-lived static credentials, limiting the damage window if a service’s credentials were ever leaked.
10.4 Secrets Management
Database credentials, payment-provider API keys, and signing secrets are never stored in application configuration files or environment variables checked into source control. Instead they live in a dedicated secrets manager, injected into each service’s runtime environment at deploy time and rotated on a schedule, with every access to a secret logged for audit purposes — an important control given that this system’s Payout Service ultimately holds credentials capable of moving real money.
10.5 Input Validation and Compliance Considerations
Every user-controlled input that reaches this system — referral codes, redirect destination parameters, UTM values, dashboard search filters — is validated against a strict allowlist pattern before use, since the Click Tracking Service’s redirect behavior is a classic target for open-redirect attacks, where an attacker crafts a referral link whose destination parameter points somewhere malicious rather than the intended product page. The Referral Link Management Service enforces that a link’s destination must resolve to a pre-registered domain under the marketplace’s own control, closing off that entire attack class at the point of link creation rather than trying to filter it at redirect time. On the compliance side, the commission ledger’s audit trail requirements typically overlap significantly with standard financial recordkeeping regulations, and many marketplaces additionally bring this system’s payout flows within scope of anti-money-laundering checks — verifying referrer identity and flagging unusually large or unusually frequent payouts for manual review before funds are released, in the same way a bank would monitor unusual account activity.
Monitoring, Logging & Metrics
Because commission money depends on this pipeline functioning correctly end to end, monitoring here needs to cover both classic system health and business-correctness signals.
11.1 Key Metrics
- Redirect latency (p50, p95, p99) from the Click Tracking Service — the single most customer-visible metric.
- Kafka consumer lag on the click-events and order-confirmed topics — rising lag means attribution and commission calculation are falling behind reality.
- Click-to-conversion rate per referrer — sudden spikes are a strong fraud signal, not just a marketing success signal.
- Commission calculation error rate — any exception during commission math should page an on-call engineer immediately, since it directly affects money owed.
- Attribution model distribution — tracking how often each attribution model is applied helps validate configuration correctness across referral programs.
11.2 Logging and Tracing
Every click, touchpoint, and commission calculation carries a correlation ID that flows through Kafka headers, so a single customer journey can be traced end-to-end across every microservice using distributed tracing tools. This is essential for resolving referrer disputes — “why was I not credited for this sale” needs to be answerable by reconstructing the exact touchpoint history and attribution decision, not by guessing.
Teams sometimes only log aggregated metrics and discard raw click events after a short retention period to save storage cost. This backfires the first time a major referrer disputes a commission calculation, because there is no way to prove or disprove what actually happened. Raw touchpoint events should be retained for at least the attribution window plus a dispute-resolution buffer, often 90 days or more.
11.3 Service Level Objectives and Alerting
Turning the metrics above into something actionable means attaching concrete Service Level Objectives (SLOs) and alert thresholds to each one, rather than just dashboarding them for passive viewing.
| SLO | Target | Alert condition |
|---|---|---|
| Redirect availability | 99.95% monthly | Page on-call if error rate exceeds 0.5% over any 5-minute window. |
| Redirect latency | p99 under 100ms | Alert if p99 exceeds 150ms sustained for 10 minutes. |
| Kafka consumer lag (attribution) | Under 60 seconds | Alert if lag exceeds 5 minutes, page if it exceeds 30 minutes. |
| Commission calculation errors | Zero tolerance | Page immediately on any exception in the commission write path. |
These thresholds are deliberately asymmetric — a redirect latency blip pages after ten minutes of sustained degradation, while any commission calculation error pages instantly, reflecting how much more costly a silent financial mistake is compared to a brief, self-recovering latency spike.
Deployment & Cloud Architecture
This system maps naturally onto a microservices deployment on any major cloud provider.
- Containerization: Every service (Click Tracking, Identity Resolution, Attribution Engine, Commission, Payout, Fraud Detection, Reporting) is packaged as an independent container and orchestrated with Kubernetes, allowing each to scale independently based on its own load profile — the Click Tracking Service needs far more replicas than the Payout Service.
- Regional deployment: For a marketplace operating across multiple geographies, the Click Tracking Service and CDN are deployed regionally close to users to minimize redirect latency, while the authoritative PostgreSQL commission ledger can remain in a primary region with read replicas elsewhere.
- Blue-green or canary deployments: Because the Attribution Engine directly affects money, changes to attribution logic are deployed via canary rollout with shadow-mode validation — running the new logic in parallel against real traffic and comparing outputs to the existing production logic before fully cutting over.
- Infrastructure as code: Load Balancer rules, API Gateway routes, Kafka topic configuration, and autoscaling policies are all defined declaratively so environments (staging, production) stay consistent.
12.1 Cost Optimization
The largest cost driver in this system is usually the click-event store, since raw click volume vastly exceeds order volume — a marketplace might see a 50-to-1 or higher ratio of clicks to conversions. A few concrete levers keep this affordable at scale: tiering older click data from Cassandra’s fast storage to cheaper cold object storage once it ages past the maximum attribution window plus a dispute-resolution buffer; compressing raw event payloads before they hit Kafka to reduce both network and storage costs; and using spot or preemptible compute instances for the Attribution Engine’s batch recomputation jobs, which are naturally fault-tolerant and re-runnable, unlike the latency-critical Click Tracking Service which needs guaranteed capacity.
12.2 Observability Tooling in the Deployment Stack
Every service in this architecture ships structured logs, metrics, and distributed traces to a centralized observability stack as a standard part of its deployment, rather than as something bolted on later per service. Metrics are scraped on a standard interval and fed into a time-series store that powers both the dashboards discussed in the monitoring section and the SLO-based alerting rules. Distributed tracing uses a shared correlation ID injected at the API Gateway and propagated through Kafka message headers, so a single customer journey — from the original click through identity resolution, attribution, and commission calculation — can be reconstructed as one trace spanning multiple services and even multiple days, which is invaluable both for debugging production issues and for answering referrer disputes about why a specific commission was or was not paid.
Databases, Caching & Load Balancing Choices
13.1 Why PostgreSQL for the Commission Ledger
Commission and payout data needs strong ACID transactional guarantees, foreign-key integrity between orders, referrers, and ledger entries, and support for complex financial queries (reconciliation, tax reporting). A relational database like PostgreSQL is the right tool here, not a NoSQL store.
13.2 Why Cassandra for Click and Touchpoint Events
Click and touchpoint data is write-heavy, append-only, naturally partitioned by visitor ID, and needs to support fast range queries (“give me all touchpoints for this visitor in the last 30 days”). Cassandra’s wide-column model and linear write scalability fit this access pattern far better than a relational database would at this volume.
13.3 Why Redis for Session and Journey Caching
Redis provides sub-millisecond lookups for referral link resolution and cached journey summaries, both of which are read constantly on the hot click path and do not need the durability guarantees of the primary stores.
13.4 Load Balancer Configuration Notes
The Load Balancer in front of the API Gateway uses layer-7 (application-aware) routing so it can make decisions based on URL path — routing /r/* click-redirect traffic to a dedicated, highly-scaled pool of Click Tracking Service instances, separate from the pool serving heavier dashboard and reporting queries, so a burst of reporting traffic can never slow down redirects.
13.5 Schema Design Notes
The Cassandra click-event table is modeled with a partition key of visitor ID and a clustering column of timestamp, which is what makes “give me all touchpoints for this visitor, ordered by time, within the attribution window” a single efficient partition read rather than a scatter-gather query across the whole cluster. The PostgreSQL commission ledger, by contrast, is modeled relationally with foreign keys tying every ledger entry back to an order ID, a referrer ID, and an attribution-run ID, with a unique constraint on order ID plus referrer ID that provides a database-level guarantee against duplicate commission entries even if application-level idempotency checks were ever bypassed by a bug. Indexes on referrer ID and status columns keep the referrer-facing dashboard queries fast without needing to scan the full ledger table.
13.6 Multi-Currency and Tax Handling
Marketplaces operating across multiple countries need the commission ledger to store both the original transaction currency and a normalized reporting currency, using an exchange rate snapshot captured at the time of the order rather than a live rate, so historical commission amounts never silently change value due to later currency fluctuation. Tax handling similarly needs to be resolved and stored at calculation time — whether a commission is treated as taxable income requiring a withholding deduction depends on the referrer’s registered tax jurisdiction, which the Commission Service looks up from the referrer’s profile before finalizing the ledger entry.
APIs & Microservices
14.1 Representative API Surface
| Endpoint | Purpose |
|---|---|
GET /r/{shortCode} | The click-redirect endpoint; public, extremely high traffic. |
POST /api/v1/links | Referrer creates a new referral link, authenticated via API key. |
GET /api/v1/dashboard/summary | Referrer dashboard view: clicks, conversions, pending and paid commission. |
POST /internal/orders/confirmed | Internal event-driven trigger (or event topic) signaling a paid order, consumed by the Attribution Engine. |
GET /api/v1/commissions/{referrerId} | Detailed, auditable commission ledger for a referrer. |
POST /internal/payouts/run | Triggers a scheduled payout batch, restricted to internal finance systems. |
14.2 Sample Request and Response
Here is what a typical dashboard summary call looks like end to end, illustrating the shape of data referrers actually consume:
GET /api/v1/dashboard/summary?range=last_30_days
Authorization: Bearer rk_live_9f3a2b...
{
"referrerId": "AJAY123",
"clicks": 18420,
"conversions": 312,
"conversionRate": 0.0169,
"pendingCommission": "48210.50",
"paidCommission": "192440.00",
"currency": "INR",
"topLinks": [
{ "shortCode": "blender9000", "clicks": 4120, "conversions": 61 },
{ "shortCode": "airfryer-x2", "clicks": 3890, "conversions": 54 }
]
}14.3 Error Handling and API Versioning
Every public endpoint returns a consistent error envelope rather than raw stack traces or provider-specific error formats, which matters a great deal for a platform with thousands of independent referrer integrations relying on predictable behavior.
| Status | Meaning | Typical cause |
|---|---|---|
| 400 | Bad Request | Malformed link creation payload, invalid date range on a dashboard query. |
| 401 | Unauthorized | Missing or invalid API key. |
| 403 | Forbidden | Valid key, but attempting to access another referrer’s data. |
| 404 | Not Found | Referral short code does not exist or has been deactivated. |
| 429 | Too Many Requests | Referrer has exceeded their API Gateway rate limit. |
| 503 | Service Unavailable | A downstream circuit breaker is open; the Gateway signals a temporary, retryable condition. |
The public API is versioned in the URL path (/api/v1/...), and breaking changes are only ever introduced in a new version, with the previous version kept live and supported for a documented deprecation window — often six to twelve months — since referrer-built integrations are external code the platform team does not control and cannot force to upgrade on a moment’s notice.
14.4 Why Microservices Here, Not a Monolith
Each service in this system has a genuinely different scaling profile, latency requirement, and failure blast radius. Click Tracking must be extremely fast and horizontally scaled to enormous instance counts; Payout Service runs rarely and needs strong consistency more than raw throughput; Fraud Detection may run expensive machine learning models. Splitting these into independent services lets each be scaled, deployed, and even written in the most appropriate technology independently, and — critically — lets a Fraud Detection bug or slowdown never risk breaking the customer-facing redirect path.
“Would you ever combine Identity Resolution and Attribution Engine into one service?” — A thoughtful answer weighs the coupling: both operate on the same journey data and could share a data store, but Attribution only needs to run at conversion time while Identity Resolution runs continuously on every click, so keeping them separate lets each scale to its own workload shape independently.
Design Patterns & Anti-Patterns
15.1 Patterns Used
Event Sourcing
Click and touchpoint data is stored as an immutable sequence of events, and current state (a journey, a commission balance) is derived by replaying or aggregating those events, rather than being mutated directly.
CQRS
Writes (click ingestion) and reads (referrer dashboard analytics) use entirely different data paths — the write path is optimized for throughput, the read path for the Data Warehouse is optimized for flexible aggregation queries.
Strategy Pattern
The Attribution Engine’s model selection (first-touch, last-touch, linear, and so on) is implemented as a swappable strategy, letting the business change attribution rules per program without modifying core engine code.
Circuit Breaker
Protects the Attribution Engine and Commission Service from cascading failures when a downstream dependency slows down.
Idempotent Consumer
Every event consumer is designed to safely process the same message more than once without corrupting commission totals.
Transactional Outbox
When the Commission Service writes a ledger entry to PostgreSQL, it writes a corresponding outbound event to an outbox table within the same database transaction, and a separate relay process publishes that event to Kafka afterward. This avoids the classic dual-write problem where a database write succeeds but the following message publish fails, leaving the system in an inconsistent state with no record that anything went wrong.
Saga Pattern
The Payout Service coordinates its multi-step process — reserve funds, call the payment provider, confirm transfer, update ledger status — as a saga with an explicit compensating action defined for each step, so a failure partway through, such as a payment provider timeout after funds were already reserved, triggers a clean rollback of the reservation instead of leaving money in an ambiguous, unreconciled state.
15.2 Anti-Patterns to Avoid
Baking last-click logic directly into checkout code makes it impossible to evolve the attribution model without a risky checkout-flow change, and scatters business logic that should live in one place across a critical, high-stakes part of the codebase that engineers are naturally reluctant to touch.
Even a “quick” fraud check on the hot click path can turn a 20ms redirect into a 2-second one under load, and a single slow downstream dependency can take down the entire referral program’s customer-facing experience.
Destroys the audit trail needed for financial disputes and regulatory compliance, and makes it effectively impossible to answer the question “what did we actually pay this referrer, and why” months after the fact.
Ignores cross-device behavior entirely and systematically undercounts legitimate multi-touch journeys, quietly biasing the whole system toward whichever referrer happens to be present in the final browser session regardless of who did the real persuasive work.
Coupling attribution logic directly inside the order database transaction means a slow or buggy attribution calculation can block or fail an otherwise perfectly valid order, conflating two concerns — “did the sale happen” and “who gets credit for it” — that should be allowed to fail independently of each other.
Best Practices & Common Mistakes
16.1 Best Practices
Store touchpoints immutably
Always store raw touchpoint events immutably, and compute attribution as a derived, replayable function — never as a side effect baked into the write path.
BigDecimal, not double
Use BigDecimal or fixed-point arithmetic for every commission calculation, never floating point.
Separate fast and slow paths
Separate the “fast path” (click redirect) from the “slow path” (attribution, commission, fraud scoring) with an asynchronous event backbone so the customer experience is never coupled to attribution complexity.
Version attribution rules
Version your attribution rules, so historical commission calculations remain explainable even after the business changes its model.
Dashboards from warehouse
Build referrer-facing dashboards from a read-optimized Data Warehouse copy, never by querying the live transactional stores directly.
Idempotency everywhere money moves
Design for idempotency everywhere money is involved — duplicate event delivery is a “when,” not an “if,” in any distributed system built on message queues.
16.2 Testing Strategy
Because commission math directly affects real payouts, this system leans heavily on a few specific testing practices beyond standard unit tests. Attribution model implementations are tested with property-based tests that assert invariants regardless of input — for example, “the sum of all attributed shares for a journey must always equal exactly 1.0, never more, never less” — which catches rounding and edge-case bugs that example-based tests alone tend to miss. The full pipeline, from a synthetic click event through to a commission ledger entry, is covered by end-to-end integration tests running against real (test-environment) Kafka and database instances rather than mocks, since the ordering and partitioning guarantees that make this system correct are exactly the kind of behavior mocks tend to hide bugs in. Finally, any change to attribution logic is validated in shadow mode against a replay of real historical journeys before being trusted with live commission calculations, comparing old-model and new-model outputs side by side for a statistically meaningful sample of orders.
16.3 Common Mistakes
- Under-provisioning the Click Tracking Service for flash-sale traffic spikes, causing slow redirects exactly when referral traffic (and revenue) is highest.
- Discarding raw click data too early, leaving no way to resolve referrer disputes months later.
- Ignoring refunds in the commission pipeline, resulting in referrers being paid for orders that were later cancelled.
- Applying fraud detection only at conversion time, missing the chance to filter fraudulent clicks before they ever pollute the touchpoint history.
Real-World / Industry Examples
The abstract architecture described throughout this tutorial is not a purely theoretical exercise — every major marketplace and affiliate network running a referral program today has converged on roughly the same set of building blocks, even though the specific technology choices, attribution windows, and commission structures differ meaningfully based on each business’s own transaction patterns, fraud exposure, and customer behavior. The examples below illustrate how the same underlying architectural principles get adapted to genuinely different business contexts.
Amazon Associates
One of the earliest large-scale affiliate programs, historically using a last-touch, 24-hour cookie model for most product categories — prioritizing simplicity and fraud resistance at massive scale over more generous multi-touch crediting. The short window reflects a deliberate trade-off: at Amazon’s transaction volume, even a small percentage increase in fraudulent or disputed attribution translates into enormous absolute cost, so simplicity and short windows reduce both fraud surface and reconciliation complexity.
Uber’s Referral Program
Uses a direct code-and-signup attribution model rather than long browsing-journey attribution, since the “conversion” (first ride) typically happens quickly after signup — showing how the attribution window and model should match the actual customer behavior of the specific business. A ride-hailing referral rarely involves a multi-week research journey the way a big-ticket electronics purchase might, so the engineering investment in sophisticated multi-touch modeling would deliver little practical benefit relative to its cost.
Impact.com and similar platforms
Third-party affiliate platforms that many marketplaces plug into offer configurable multi-touch attribution models (linear, time-decay, position-based) as a core product feature, reflecting how central flexible attribution has become to modern affiliate marketing. These platforms typically expose attribution model selection as a per-program configuration rather than a code-level decision, echoing the Strategy pattern design discussed earlier in this tutorial.
Rakuten Advertising
Operates cross-device identity resolution and multi-touch attribution at large scale across thousands of merchants, illustrating the identity-graph and event-backbone architecture described in this tutorial applied across an entire affiliate network rather than a single marketplace. Operating across many independent merchants also means their identity resolution has to work without the benefit of a single shared login system, leaning more heavily on probabilistic device signals than a single-marketplace system would need to.
Cashback and Coupon Aggregator Apps
Cashback apps sit at a structurally interesting position in the attribution journey: they are frequently the very last touchpoint before checkout, since users often open them right before paying specifically to find a discount code. This is exactly the scenario that makes pure last-click attribution controversial in the industry — it can systematically reward the coupon app that intercepted an already-decided purchase over the content creator who did the actual persuasion work days earlier, which is a major reason many large programs have moved toward position-based or time-decay models instead.
Employee & Customer Referral Programs
Many marketplaces also run an internal referral program where existing customers refer friends directly, typically using a simple unique code rather than a trackable link, with attribution resolved at account signup rather than through browsing-journey reconstruction. This is a useful contrast case: because the referral relationship is captured explicitly at signup, this program flavor needs almost none of the click-tracking or identity-resolution machinery this tutorial focuses on, showing how the right architecture always depends on the specific shape of the referral relationship being modeled.
Appendix: Sample Referral Program Configuration
To make the abstract discussion of attribution models, windows, and commission rates concrete, here is what a real program configuration record might look like in the Referral Link Management Service, and a short walkthrough of how each field is actually used elsewhere in the pipeline.
{
"programId": "influencer-program-2026",
"attributionModel": "POSITION_BASED",
"attributionWindowDays": 21,
"defaultCommissionPercent": 0.08,
"maxCommissionPerOrder": 5000.00,
"minimumOrderValue": 200.00,
"eligibleCategories": ["electronics", "home-appliances"],
"fraudScoreThreshold": 70,
"payoutSchedule": "MONTHLY",
"currency": "INR"
}The attributionModel and attributionWindowDays fields feed directly into the Attribution Engine’s strategy selection and eligibility filtering, described in section 5.4. The defaultCommissionPercent and maxCommissionPerOrder fields are read by the Commission Calculation Service during the ledger-writing step shown in section 5.6, while minimumOrderValue lets the business exclude very low-value transactions from earning any commission at all, protecting margin on small orders where an eight percent commission would barely cover the platform’s own transaction costs. The fraudScoreThreshold ties directly into the Fraud Detection Service’s scoring pipeline from section 5.7, and eligibleCategories lets a program restrict itself to specific product categories rather than the whole marketplace catalog — useful, for example, when an electronics-focused influencer program should not accidentally pay out commission on unrelated grocery orders that happened to occur within the same attribution window. Because every one of these fields is data, not code, launching a brand-new referral program with entirely different rules requires no engineering deployment at all — just a new configuration record, which is precisely the flexibility a hardcoded, checkout-embedded last-click system could never offer.
Glossary of Terms
| Term | Meaning |
|---|---|
| Referral code | A short unique identifier tied to a specific referrer, embedded in a referral link. |
| Touchpoint | Any recorded interaction a customer had that could plausibly influence a future purchase. |
| Attribution window | The maximum allowed time between a touchpoint and a purchase for that touchpoint to still receive credit. |
| Attribution model | The mathematical rule used to split credit for a sale among multiple touchpoints in a journey. |
| Identity resolution | The process of merging anonymous, cookie-based sessions into one customer identity, typically triggered by a login event. |
| Deferred deep linking | A technique for passing referral context through an app-store install, since a native app cannot read a mobile browser’s cookies. |
| Cookie stuffing | A fraud technique where referral cookies are silently dropped on visitors who never actually clicked or saw the referral link. |
| Clawback | A negative, compensating commission ledger entry issued when a previously-commissioned order is refunded or cancelled. |
| Idempotent consumer | A message consumer designed so that processing the same event twice never produces an incorrect or duplicated result. |
| Shadow mode | Running new logic in parallel with production logic on real traffic, without affecting real outcomes, purely to validate correctness before cutover. |
FAQ, Summary & Key Takeaways
20.1 Frequently Asked Questions
Why not just use the last click for everything? It is so much simpler.
Last-click attribution is simple and fraud-resistant, but it systematically undervalues top-of-funnel referrers who introduce customers to a product days or weeks before purchase, which over time discourages exactly the kind of high-quality content marketing many marketplaces want to encourage.
How do you handle a customer who clears cookies between touchpoints?
Cookie clearing genuinely breaks anonymous identity continuity. The system mitigates this through login-based identity resolution — once a customer logs in on any device, prior anonymous sessions matching device or network signals can sometimes be probabilistically merged, but a fully cookie-cleared, never-logged-in journey will unavoidably lose some touchpoint history. This is a known, accepted limitation across the industry.
What happens if two referrers both claim the same click?
A single click event is tied to exactly one referral code at capture time — there is no ambiguity there. The more common real scenario is multiple different touchpoints from different referrers across one journey, which is precisely what multi-touch attribution models are designed to split fairly.
How do refunds affect already-paid commissions?
A refund generates a compensating negative ledger entry (a clawback) rather than deleting the original commission record, preserving the audit trail. If the commission was already paid out, the clawback is typically deducted from the referrer’s next payout cycle rather than demanding an immediate repayment.
Can the attribution model differ per referral program?
Yes — since the Attribution Engine implements attribution models as a swappable strategy, different referral programs (an influencer program versus an employee-referral program versus a cashback-app integration) can each be configured with the model that best fits their business goal.
How do you prevent a referrer from seeing another referrer’s data?
Authorization is enforced at two layers: the API Gateway resolves every request to exactly one authenticated referrer identity from their API key or dashboard session, and every downstream query is additionally scoped to that referrer ID at the database layer as a defensive second check, so even a bug in one layer alone cannot leak cross-referrer data.
Why is the Click Tracking Service kept so deliberately simple?
Every additional piece of logic on the click-redirect path — a synchronous fraud check, a database write instead of an async event, an extra network hop — adds latency that is multiplied across the highest-traffic endpoint in the entire system. Keeping it thin and pushing all complexity into asynchronous downstream services is one of the most important architectural decisions in this design.
How would you migrate from a last-click system to full multi-touch attribution without disrupting existing referrers?
Run the new multi-touch Attribution Engine in shadow mode first — computing attribution in parallel with the existing last-click logic without actually changing payouts — for a full attribution-window cycle, comparing the outputs and validating that overall commission totals move in expected, explainable directions before cutting production traffic over to the new model.
What is the single most important design decision in this entire system?
Separating the latency-critical click-redirect path from every other piece of business logic through an asynchronous event backbone. Almost every other design choice in this tutorial — Kafka as the backbone, stateless services, eventual consistency for click data, strong consistency only for money — flows directly from protecting that one hot path.
How would this design change for a marketplace with far smaller traffic, say a few hundred orders a day?
At that scale, much of this architecture’s complexity — Kafka, Cassandra, a dedicated Identity Resolution Service — would be over-engineering. A simpler design with a single relational database, a scheduled batch job computing attribution nightly, and a basic cookie-based click log would likely serve the business better, with room to evolve toward this fuller architecture only once real traffic and fraud pressure justify the added operational cost.
How do you handle a referrer who disputes a specific commission calculation months after the fact?
Because every touchpoint, attribution decision, and commission ledger entry is stored immutably with a correlation ID tying them together, a support engineer can reconstruct the exact customer journey, the exact attribution model version that was active at the time, and the exact math applied — turning what could be a “trust me” conversation into a fully reproducible, evidence-backed explanation. This is precisely why the design insists on immutability and full historical retention rather than discarding raw data once a commission has been paid: disputes are not a rare edge case in an affiliate program handling real money, they are a routine, expected part of operating one, and the system needs to be built to answer them confidently from day one rather than scrambling to reconstruct history after the fact.
20.2 Summary
Designing an affiliate and referral attribution system is ultimately an exercise in building a trustworthy, auditable record of causality — deciding, fairly and defensibly, who deserves credit when a sale happens after a customer’s attention was captured by several different sources over days or weeks. The architecture in this tutorial makes that possible by separating concerns cleanly: a thin, extremely fast Click Tracking Service sitting behind a Load Balancer and API Gateway captures raw signal without slowing anyone down; an asynchronous event backbone built on Kafka decouples that fast path from every piece of downstream complexity; an Identity Resolution Service stitches fragmented sessions into coherent journeys; an Attribution Engine applies a configurable, swappable mathematical model to split credit; and a Commission Service, backed by a strongly consistent relational store, turns that credit into real, auditable money. Every one of these pieces exists because a simpler, more naive design — cookie-only last-click attribution baked into checkout code — breaks down under real-world scale, real-world fraud incentives, and real-world customer behavior that spans devices, sessions, and weeks. Understanding why each component exists, not just what it does, is what separates a system that merely works from one that survives contact with production traffic, hostile actors, and finance-team audits.
Key Takeaways
- Attribution is fundamentally a data-modeling problem: capture every touchpoint immutably, and treat “who gets credit” as a derived calculation, not a hardcoded rule.
- The click-redirect path is latency-critical and must be decoupled from all downstream attribution, fraud, and commission complexity through an asynchronous event backbone.
- Every service in this architecture sits behind a Load Balancer and API Gateway, which together handle traffic distribution, authentication, and routing before any business logic executes.
- Different attribution models (first-touch, last-touch, linear, time-decay, position-based) represent genuine business trade-offs, not just implementation details.
- Because real money is at stake, immutability, idempotency, and auditability are not optional extras — they are core correctness requirements of the system.
- Fraud detection must be layered throughout the pipeline, not bolted on only at the final commission-calculation step.
The best attribution systems are ones you never notice: referrers get paid fairly and on time, disputes are resolved with data rather than opinion, and the customer clicking a link never sees anything but a fast, invisible redirect to the product they wanted. All of the architectural complexity in this tutorial exists in service of that simple, quiet outcome.