Designing an Influencer Marketing Attribution Platform
How do you track a sale that happens on Shopify, Amazon, and a brand’s own website — and prove, with confidence, that it happened because of a video a creator posted three days ago on Instagram? This deep dive builds that system from first principles, piece by piece.
Introduction & History
Imagine you run a skincare brand. You send a bottle of serum to a creator with 400,000 followers. She posts a video. Over the next two weeks, some of her followers buy your serum — a few directly from a link in her Instagram bio, some by typing a discount code at checkout on your Shopify store, a few more by searching your brand on Amazon after seeing the video, and a couple by clicking a link she shared in her newsletter which took them to your website. Your job, as the platform builder, is to answer one deceptively hard question: how many of those sales actually happened because of her, and how much should she be paid?
This is the job of an influencer marketing attribution platform. It is a system that connects two worlds that were never designed to talk to each other: the world of content (videos, posts, stories) and the world of commerce (checkouts, order confirmations, refunds), across dozens of different e-commerce systems that each expose different, incomplete, and sometimes contradictory signals.
A Short History of “Who Gets the Credit”
Attribution as a discipline is much older than influencer marketing. In the 1990s and 2000s, affiliate marketing networks solved a narrower version of this problem: a publisher placed a link, a cookie was dropped in the shopper’s browser, and if a purchase happened within a cookie window (commonly 30 days), the affiliate got credit. This “last-click” model was simple, and simplicity is exactly why it survived for two decades — it was easy to compute and easy to explain to a publisher who wanted their check.
Around 2015-2018, as Instagram and YouTube became serious sales channels, brands realized last-click attribution badly undercounted influencer impact. A shopper might watch a video, feel inspired, close the app, and buy the product two days later by searching for the brand directly — with no cookie, no click, and no code entered. The affiliate model saw nothing. This gap is what pushed platforms toward richer attribution: unique discount codes per creator, dedicated landing pages, pixel tracking, and eventually probabilistic and multi-touch models that try to reconstruct a shopper’s journey even when direct tracking data is missing.
Today’s influencer attribution platforms sit at the intersection of adtech (cookies, pixels, UTMs), affiliate marketing (codes, commission engines), and modern event-streaming architecture (because the volume of “someone clicked a link” and “someone bought a product” events is enormous and constant).
Amazon Associates — the modern affiliate era begins
Amazon’s affiliate program formalized cookie-and-link attribution for e-commerce at scale, giving every website owner a way to earn commission on referred sales — the template every affiliate network still borrows from.
UTM parameters and web analytics
Urchin (later Google Analytics) popularized appending campaign parameters to URLs so downstream systems could distinguish where visitors came from — still the simplest attribution signal on the web.
Instagram opens the influencer floodgates
As short-form image and video content matured, brands began paying creators directly to promote products — but attribution tooling lagged, forcing brands to eyeball spreadsheets of discount-code redemptions.
Dedicated influencer attribution platforms mature
Tools like LTK, Grin, Impact.com, and Refersion emerge to solve the specific creator-commerce attribution problem that generic affiliate networks and web analytics could not.
Cookie deprecation forces server-side rethink
Safari’s ITP and other browser-level anti-tracking measures made cookie-based attribution unreliable, pushing the industry toward discount codes, server-side pixels, and identity graphs.
Multi-touch, AI-assisted attribution goes mainstream
Brands increasingly demand time-decay, linear, and position-based multi-touch models that reconstruct entire shopper journeys rather than crediting only the last click before purchase.
Think of a talent agent trying to prove their actor caused a movie to sell more tickets. Box office receipts don’t say “this ticket was sold because of that actor.” The agent has to piece together evidence: trailer views, search trends after interviews aired, ticket sales in cities where the actor did press. An attribution platform does the same job for creators and products, except it has to do it automatically, for millions of transactions, in near real time.
Problem & Motivation
Before drawing a single box on an architecture diagram, we need to be precise about what makes this problem hard. It is not “build a dashboard.” It is a distributed data-collection and reconciliation problem with real money attached.
2.1 The Core Challenges
Fragmented commerce surfaces
A single brand might sell through Shopify, WooCommerce, Amazon Seller Central, a custom-built storefront, and in-app checkouts on TikTok Shop. Each exposes a different API, different webhook payload, different definition of “order confirmed,” and different latency between purchase and data availability.
Weak or missing identifiers
A creator’s audience doesn’t log into a unified “creator platform account” before buying a moisturizer. The only bridges between “saw the content” and “bought the product” are fragile signals: a coupon code, a UTM parameter, a click-through link, or a fuzzy time-and-geography correlation.
Multi-touch journeys
Real shoppers see content from more than one creator, on more than one platform, before buying. Assigning 100% of the credit to the last click is easy but often wrong; splitting credit fairly across five touchpoints is mathematically and operationally harder.
Money is on the line
Attribution errors are not just analytics inaccuracies — they directly change how much a creator is paid. Overcounting drains the brand’s marketing budget; undercounting underpays creators and damages trust. The system must be auditable, not just fast.
2.2 Why Existing Tools Fall Short
- Generic web analytics (like a plain pageview tracker) captures traffic but has no concept of a “creator,” a “commission,” or a “payout.”
- Plain affiliate networks handle codes and links reasonably well but were built for a world of static banner ads, not short-form video, stories that vanish in 24 hours, and creators publishing across five platforms at once.
- E-commerce platforms themselves know a sale happened but have no idea a creator caused it, unless the brand manually reconciles discount code usage in a spreadsheet — which does not scale past a handful of creators.
Platforms like LTK (formerly LIKEtoKNOW.it), Grin, Impact.com, and Amazon’s own Creator Connections program each built purpose-specific attribution engines because none of the above generic tools were sufficient. Creator commerce needed its own category of system.
2.3 What This System Must Guarantee
Traceable dollars
Every dollar of commission must trace back to verifiable events. Creators and finance teams will audit payouts; unexplainable numbers destroy trust.
Near real-time visibility
Creators want to see clicks and early sales within minutes, not next-day batch reports — especially during a live campaign moment.
Idempotent processing
Webhooks retry, e-commerce platforms resend events; double-counting a sale is a serious bug that directly corrupts payouts.
Multiple attribution models
Different brands have different rules — last-touch, first-touch, or multi-touch weighted models — and the same underlying data must serve all of them.
Horizontal scalability
Click and impression volume for a single viral video can generate millions of clicks in hours; the ingestion tier must expand horizontally to absorb it.
2.4 The Scale of the Problem
It helps to ground the design in real numbers before drawing boxes. A mid-market influencer platform might coordinate tens of thousands of active creators across a given quarter, each publishing content on two or three platforms, driving millions of link clicks and hundreds of thousands of orders per month across thousands of connected brand stores. During a coordinated campaign moment — a major creator’s video going viral, or a seasonal sale event — click volume for a single campaign can spike fifty to a hundred times its baseline within minutes. Meanwhile, the underlying financial stakes are real: commission payouts for a platform at this scale can run into millions of dollars a month, meaning attribution mistakes are not abstract data-quality issues but direct, auditable financial errors.
This combination — bursty, unpredictable write volume on one side, and strict correctness requirements on the money-bearing side — is what pushes the design toward the event-driven, polyglot-persistence architecture explored throughout this tutorial, rather than a simpler synchronous, single-database approach that might suffice for a much smaller system.
“Why can’t you just use last-click attribution for everything?” — Be ready to explain that last-click is cheap and simple but systematically undercounts upper-funnel influence (a shopper who watched a video but bought two days later via direct search gets no attribution), and that brands increasingly demand multi-touch models even though they are computationally and organizationally harder to implement and explain.
Core Concepts
Before the architecture makes sense, we need a shared vocabulary. Each concept below is explained with what it is, why it exists, and a simple example.
3.1 Tracking Link / Deep Link
A tracking link is a unique URL generated per creator, per campaign, sometimes per individual post, that redirects the shopper to the real product page while recording that this particular creator sent this particular visitor.
Instead of sharing brand.com/serum, a creator shares brand.link/aisha-serum-jul. When someone clicks it, the platform’s redirect service logs “click from Aisha’s July campaign link” and then bounces the browser to the real product page — usually with extra parameters attached.
3.2 UTM Parameters
UTM (Urchin Tracking Module) parameters are small pieces of text appended to a URL, like ?utm_source=aisha&utm_medium=instagram&utm_campaign=summer_serum, that let downstream analytics tools know where a visitor came from. They are the oldest, simplest attribution signal on the web, dating back to Google Analytics’ predecessor, Urchin, in the early 2000s.
3.3 Discount / Promo Codes as Identifiers
A unique code like AISHA20 serves two purposes at once: it gives the shopper a real incentive to buy, and it gives the brand a durable attribution signal that survives even if the shopper closes the browser, switches devices, and buys three days later without clicking any link at all.
This is why nearly every creator you follow has a personal discount code — it is not just a marketing gimmick, it is the single most reliable attribution mechanism available today, because it survives cross-device and delayed purchases where cookies and pixels fail.
3.4 Pixel / Conversion Tag
A pixel is a tiny snippet of tracking code (historically a 1×1 transparent image, now usually a JavaScript snippet) placed on a brand’s order-confirmation page. When a purchase completes, the pixel fires a request back to the attribution platform carrying order value, product IDs, and any cookie or click identifiers it can find.
3.5 Attribution Window
The attribution window is the maximum time allowed between a touchpoint (click, view) and a purchase for the platform to still credit that touchpoint. A 7-day click window means: if the shopper clicks Aisha’s link and buys within 7 days, Aisha gets credit; on day 8, she does not.
3.6 Attribution Models
Last-touch
100% of credit to the most recent touchpoint before purchase. Used by simple affiliate-style programs because it’s easy to explain and cheap to compute.
First-touch
100% of credit to the touchpoint that started the journey. Favored by brands optimizing for awareness/discovery creators who plant the initial seed.
Linear multi-touch
Credit split evenly across all touchpoints inside the window. Used by brands running multi-creator campaigns who want an unambiguous fairness rule.
Time-decay
Touchpoints closer to the purchase get more credit than earlier ones (exponential decay). Balances discovery credit against closing influence.
Position-based
First and last touch get heavier weights (e.g. 40/40); middle touches share the remaining 20%. Values both discovery and conversion equally.
3.7 Conversion Event vs. Order Event
A conversion event is the platform’s internal record that “this touchpoint led to this outcome.” An order event is the raw signal from the e-commerce system (Shopify webhook, Amazon report row) saying a purchase happened. The system’s job is to turn many raw order events, correlated against many touchpoints, into correctly-weighted conversion events.
3.8 Consistency Models and the CAP Theorem, Applied Here
The CAP theorem states that a distributed system can only fully guarantee two of three properties at once during a network partition: Consistency (every read sees the latest write), Availability (every request gets a response), and Partition tolerance (the system keeps working despite network splits between nodes). Since network partitions are a fact of life in any real distributed deployment, the practical choice is really between consistency and availability when a partition occurs.
This system deliberately makes different choices for different components, because not every piece of data needs the same guarantee:
Click/view ingestion
Availability over strict consistency. Losing strict ordering of a few clicks during a partition is far cheaper than refusing to accept clicks at all — a dropped click is invisible; a refused click loses a shopper.
Commission ledger & payouts
Consistency over availability. Paying a creator twice for the same sale, or losing a payout record, is a much worse outcome than brief unavailability during a partition.
Campaign & contract config (PostgreSQL)
Strong consistency. A brand editing commission terms must never have two different services reading two different, conflicting versions of the rules.
Think of an airport. The departure boards (dashboards, analytics) can lag reality by a few seconds without causing harm — that’s an availability-favoring, eventually-consistent system. But the gate agent scanning boarding passes must have a strictly consistent, up-to-the-second view of who has already boarded, because letting the same seat be assigned twice is a real, costly failure — that’s the commission ledger’s consistency-favoring design.
Expect: “How would you design the attribution window and handle a shopper who clicks two different creators’ links before buying?” A strong answer references the multi-touch models above and explains that the system must store an ordered list of touchpoints per shopper identity, not just the last one, to support anything beyond last-touch. A good follow-up connects this to CAP: the touchpoint list can tolerate eventual consistency, but once it feeds a payout calculation, that calculation must be strongly consistent and idempotent.
3.9 Commission Structures
Attribution answers “who caused this sale”; commission structure answers “how much do they earn for it.” These are deliberately kept as separate concerns in the design, because the same attributed sale can be compensated very differently depending on the individual contract a brand negotiated with a creator.
Flat fee per sale
A fixed amount regardless of order value, e.g. $5 per attributed order. Used for low-priced, high-volume products where percentage fees would be too small to matter.
Percentage of order value
A commission rate applied to the order total, e.g. 15% of $80 = $12. The most common structure; scales naturally with basket size.
Tiered / bonus
Base rate increases once a creator crosses a volume threshold, e.g. 10% for the first 50 sales, 15% after. Incentivizes top performers to keep promoting past the initial burst.
Flat content fee + performance bonus
A guaranteed payment for posting content, plus additional performance-based commission on top. Common for larger creators who require a guaranteed minimum.
Keeping commission rules as data (stored per creator-campaign contract in PostgreSQL, as shown in Section 13.5) rather than code means a brand can change a creator’s rate without a deployment, and the Commission Calculation Service simply reads the currently active contract terms when processing each confirmed conversion record.
Architecture & Components
With vocabulary settled, here is the system decomposed into services. Each service owns one responsibility and communicates with others through well-defined APIs or events — never by reaching into another service’s database.
4.1 Component Responsibilities
Link Redirect Service
Generates and resolves short tracking links. On every click, it records a click event (creator, campaign, timestamp, visitor cookie/device ID, referrer) and issues a fast HTTP redirect (target: under 100ms) so the shopper barely notices the hop.
Pixel Collector Service
Receives conversion pings fired from brand checkout pages. Extracts order value, currency, product SKUs, and whatever identifiers (cookie, click ID, discount code used) are available.
Webhook Ingestion Gateway
A single, hardened entry point that receives inbound webhooks from every connected e-commerce platform, verifies signatures, normalizes payloads into a common internal schema, and publishes them onto the event backbone.
Identity Resolution Service
Stitches together fragmented signals — cookies, device IDs, email hashes, discount codes — into a best-effort “shopper journey,” so the Attribution Engine has an ordered list of touchpoints to work with instead of disconnected events.
Attribution Engine
Applies the campaign’s configured attribution model (last-touch, linear, time-decay, etc.) to a resolved shopper journey and produces conversion credit records.
Commission Calculation Service
Applies each creator’s contract terms (flat fee per sale, percentage of order value, tiered bonuses) to conversion credit records to compute what is owed.
Fraud & Anomaly Detection
Flags suspicious patterns: click farms, self-purchases, coupon code abuse, or an implausible spike in conversions from a single IP range, before commissions are finalized.
Payout Service
Batches approved commissions into scheduled payment runs, integrates with payment providers, and maintains an immutable ledger for audits.
4.1b Onboarding a New E-commerce Integration
Connecting a new brand’s store to the platform follows a repeatable lifecycle rather than a one-off manual setup, which matters once the platform is managing integrations for thousands of brands simultaneously. First, the brand authorizes the platform via OAuth (for platforms like Shopify) or provides API credentials (for platforms without OAuth support). Second, the platform programmatically registers the required webhook subscriptions — order creation, order update, refund — directly through the e-commerce platform’s own API rather than asking the brand to configure anything manually. Third, a validation step sends a test event through the full pipeline end-to-end and confirms it arrives correctly normalized before the integration is marked active. Finally, ongoing health checks periodically verify the webhook subscription is still valid, since brands sometimes inadvertently revoke API access while reconfiguring their own store settings, and catching that quickly prevents a silent gap in attributed sales that could otherwise go unnoticed for weeks.
4.2 The API Gateway as the Front Door
Sitting in front of every client-facing service (Dashboard API, Payout Service) is a single API gateway that handles concerns every service would otherwise have to duplicate: authentication token validation, rate limiting per API key, request logging, and TLS termination. This keeps individual services focused purely on their business logic, and gives the platform one consistent place to roll out a security fix — such as tightening token expiry — without touching every downstream service individually.
“Why not put attribution logic directly inside the webhook handler?” Good answer: coupling ingestion (which must be fast and always-available to avoid losing e-commerce webhooks) with attribution logic (which is compute-heavier and may need reprocessing as models change) violates single responsibility and makes it impossible to scale or redeploy the two independently. Decoupling them via an event backbone also allows replaying historical events if the attribution model changes.
Internal Working
5.1 How a Click Becomes a Tracked Event
When a shopper clicks a creator’s tracking link, the Link Redirect Service must do three things in a strict order, all within a tight latency budget: record the click, set or read an identity cookie, and redirect. Here is a simplified Java implementation of the redirect handler:
@RestController
public class LinkRedirectController {
private final TrackingLinkRepository linkRepo;
private final KafkaTemplate<String, ClickEvent> kafkaTemplate;
// GET /r/{shortCode}
@GetMapping("/r/{shortCode}")
public ResponseEntity<Void> handleRedirect(
@PathVariable String shortCode,
@CookieValue(value = "visitor_id", required = false) String visitorId,
HttpServletRequest request,
HttpServletResponse response) {
TrackingLink link = linkRepo.findByShortCode(shortCode)
.orElseThrow(() -> new LinkNotFoundException(shortCode));
// Assign a durable visitor identifier if one doesn't exist yet
String resolvedVisitorId = (visitorId != null) ? visitorId : UUID.randomUUID().toString();
Cookie cookie = new Cookie("visitor_id", resolvedVisitorId);
cookie.setMaxAge(60 * 60 * 24 * 90); // 90-day attribution-adjacent cookie
cookie.setHttpOnly(true);
cookie.setSecure(true);
response.addCookie(cookie);
ClickEvent event = ClickEvent.builder()
.visitorId(resolvedVisitorId)
.creatorId(link.getCreatorId())
.campaignId(link.getCampaignId())
.destinationUrl(link.getDestinationUrl())
.ipAddress(request.getRemoteAddr())
.userAgent(request.getHeader("User-Agent"))
.clickedAt(Instant.now())
.build();
// Fire-and-forget publish; the redirect must not wait on Kafka
kafkaTemplate.send("clicks", resolvedVisitorId, event);
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create(appendTrackingParams(link.getDestinationUrl(), link, resolvedVisitorId)))
.build();
}
}Notice the redirect returns immediately without waiting for the Kafka publish acknowledgment. This is a deliberate trade-off: shoppers abandon slow redirects within a few hundred milliseconds, so the click-recording path is optimized for speed over strict durability, with the understanding that a small percentage of clicks may be lost during a broker outage — an acceptable trade-off explored further in Section 7.
5.2 How a Webhook Becomes a Normalized Order Event
Every connected e-commerce platform sends a different payload shape. The Webhook Ingestion Gateway’s job is to translate all of them into one canonical OrderEvent schema before anything downstream ever sees it.
public interface WebhookNormalizer {
OrderEvent normalize(String rawPayload, PlatformType platform);
}
@Component
public class ShopifyWebhookNormalizer implements WebhookNormalizer {
@Override
public OrderEvent normalize(String rawPayload, PlatformType platform) {
ShopifyOrderPayload payload = objectMapper.readValue(rawPayload, ShopifyOrderPayload.class);
return OrderEvent.builder()
.externalOrderId(payload.getId())
.platform(PlatformType.SHOPIFY)
.orderValue(payload.getTotalPrice())
.currency(payload.getCurrency())
.discountCode(extractDiscountCode(payload))
.customerEmailHash(sha256(payload.getEmail()))
.lineItems(mapLineItems(payload.getLineItems()))
.status(mapStatus(payload.getFinancialStatus()))
.occurredAt(payload.getCreatedAt())
.receivedAt(Instant.now())
.build();
}
}This is exactly like a universal power adapter for international travel. Every country’s socket (Shopify, Amazon, WooCommerce) has a different shape, but the adapter always outputs the same standard plug shape on the other side, so every device (downstream service) can plug in without caring which country it came from.
5.3 Identity Resolution: Stitching a Journey Together
The hardest internal problem is connecting a click event, a view event, and an order event into one coherent shopper journey when none of them share a perfect common key. The Identity Resolution Service uses a waterfall of matching strategies, from strongest to weakest signal:
- Exact click ID match — the order carries the same click ID that was appended to the tracking link (highest confidence).
- Discount code match — the order used a code uniquely assigned to one creator (very high confidence, survives cross-device).
- Cookie/visitor ID match — the pixel on the confirmation page read the same
visitor_idcookie set during the click (high confidence, same-device only). - Hashed email match — the shopper’s hashed email appears in both a prior interaction (e.g. a giveaway entry) and the order (medium confidence).
- Probabilistic/time-geo correlation — no direct identifier exists, but the order occurred shortly after a click from the same rough IP block and device fingerprint (lowest confidence, used only if the brand opts in).
“What happens when two different matching strategies point to two different creators for the same order?” The correct answer is that the waterfall should be strictly ordered by confidence and stop at the first successful match, rather than trying to average across signals of very different reliability — mixing a near-certain discount-code match with a probabilistic geo-guess would corrupt trustworthy data with noisy data.
5.3b Computing Time-Decay Credit
Of the attribution models introduced in Section 3.6, time-decay is the most algorithmically interesting, since it requires weighting each touchpoint by how recently it occurred relative to the purchase, using an exponential decay function rather than a flat split.
public Map<String, BigDecimal> assignTimeDecayCredit(
List<Touchpoint> journey, BigDecimal orderValue, Instant purchaseTime, double halfLifeDays) {
double decayConstant = Math.log(2) / halfLifeDays;
Map<String, Double> rawWeights = new HashMap<>();
double totalWeight = 0.0;
for (Touchpoint tp : journey) {
double daysBeforePurchase = Duration.between(tp.getOccurredAt(), purchaseTime).toHours() / 24.0;
double weight = Math.exp(-decayConstant * daysBeforePurchase);
rawWeights.merge(tp.getCreatorId(), weight, Double::sum);
totalWeight += weight;
}
Map<String, BigDecimal> credit = new HashMap<>();
for (Map.Entry<String, Double> entry : rawWeights.entrySet()) {
double share = entry.getValue() / totalWeight;
credit.put(entry.getKey(), orderValue.multiply(BigDecimal.valueOf(share))
.setScale(2, RoundingMode.HALF_UP));
}
return credit;
}With a 3-day half-life, a touchpoint from 3 days before purchase is worth half as much as one from the moment of purchase, and one from 6 days before is worth a quarter — a smooth, tunable way to reward recency without discarding earlier touchpoints entirely the way pure last-touch would.
5.4 Concurrency and Consensus
Two different failure modes have to be handled carefully once multiple consumer instances are processing events in parallel.
Concurrent Processing of the Same Shopper Journey
If a shopper’s click event and their order event happen to be processed by two different consumer threads at nearly the same moment (for example, during a batch reprocessing job triggered by a model change), both threads might try to write conflicting touchpoint records for the same visitor at once. The system avoids this with optimistic concurrency control: each journey record carries a version number, and a writer must read the current version before committing an update; if the version has changed since it was read, the write is rejected and retried against the fresh state, rather than silently overwriting another thread’s work.
@Transactional
public void appendTouchpoint(String visitorId, Touchpoint touchpoint) {
int maxRetries = 3;
for (int attempt = 0; attempt < maxRetries; attempt++) {
ShopperJourney journey = journeyRepo.findByVisitorId(visitorId);
long expectedVersion = journey.getVersion();
journey.addTouchpoint(touchpoint);
int rowsUpdated = journeyRepo.updateIfVersionMatches(journey, expectedVersion);
if (rowsUpdated == 1) {
return; // success
}
// version mismatch: someone else updated concurrently, retry
}
throw new ConcurrentUpdateException("Failed to append touchpoint after retries: " + visitorId);
}Leader Election for Scheduled Batch Jobs
Reconciliation sweeps, payout batch runs, and nightly aggregate rebuilds must run exactly once, even though the service that performs them is deployed across multiple replicas for availability. Rather than letting every replica fire the job simultaneously (which would produce duplicate payout runs), the system uses a distributed lock, backed by a coordination service such as ZooKeeper or a database-based lease, so only the replica that successfully acquires the lease runs the job; the others stand by as hot backups ready to take over if the leader crashes mid-run.
This is the same problem as a relay race with several runners standing ready at the same baton exchange point — only one of them should actually pick up the baton and run. A leader-election lease is the whistle that says “you, specifically, go now,” so the other runners know to wait rather than all sprinting off with their own copy of the baton.
Data Flow & Lifecycle
Let’s trace one purchase end-to-end, from the moment a creator posts content to the moment she is paid.
Creator publishes content with a tracking link
A creator posts a video or story on Instagram/TikTok that includes a unique short link generated by the platform for this creator + campaign combination.
Shopper clicks link → click event to Kafka
The Link Redirect Service records a click, sets the visitor_id cookie, publishes to the clicks topic, and issues a 302 redirect to the product page — all in under 100 ms.
Shopper browses and (later) checks out
Between click and checkout the shopper may leave, come back, switch devices, or hunt for a discount code — the identity graph will have to reconnect these fragments later.
Store fires webhook + confirmation pixel
The e-commerce platform delivers an order-created webhook to the Webhook Ingestion Gateway; the confirmation page fires the platform’s conversion pixel in parallel.
Webhook Gateway normalizes into OrderEvent
Signatures are verified, the platform-specific payload is transformed into the canonical internal schema, and the event is published to the orders topic.
Identity Resolution stitches the journey
Using the waterfall in Section 5.3, the service assembles an ordered list of touchpoints for this shopper across every prior click, view, or code redemption.
Attribution Engine assigns credit
The campaign’s configured model (last-touch, linear, time-decay) is applied to the resolved journey, producing one or more conversion-credit records.
Commission Service computes payout amount
Each conversion credit is multiplied by the creator’s current contract terms (flat, percentage, tiered) to produce a provisional commission entry.
Fraud check
The Fraud & Anomaly service evaluates the record against per-brand rules and ML signals; suspicious records are moved into a manual-review queue rather than auto-approved.
Reconciliation window → payout batch
Records that pass the reconciliation window (30–45 days without refund or fraud flag) are added to the next payout batch and the creator is paid; the ledger records the transaction immutably.
6.1 Handling Delayed and Out-of-Order Events
E-commerce reality is messy. A refund might arrive a week after the original order. A webhook might be delayed by hours during a platform outage and arrive out of order relative to a later order. The Attribution Engine must treat conversion records as mutable within a reconciliation window rather than permanently final the instant they are created.
A common approach, similar to how ad-tech platforms handle “conversion lag,” is to keep a rolling 30–45 day reconciliation window during which attributed sales can still be revised (e.g. downgraded if the order is refunded, or removed if it’s later flagged as fraudulent) before a payout is finally locked and paid.
6.2 Idempotency
Webhooks from platforms like Shopify are explicitly documented as “at-least-once” — the same order-created webhook can be delivered more than once. Every event consumer in this system must be idempotent, typically by upserting on (platform, external_order_id) rather than blindly inserting, so a retried webhook never creates a duplicate commission.
@Transactional
public void handleOrderEvent(OrderEvent event) {
String idempotencyKey = event.getPlatform() + ":" + event.getExternalOrderId();
boolean alreadyProcessed = processedEventRepo.existsByIdempotencyKey(idempotencyKey);
if (alreadyProcessed) {
log.info("Duplicate order event ignored: {}", idempotencyKey);
return; // safe no-op, not an error
}
processedEventRepo.save(new ProcessedEvent(idempotencyKey, Instant.now()));
attributionEngine.process(event);
}6.2b Retries, Backpressure, and Dead-Letter Handling
Not every failure during processing is the same kind of failure, and treating them identically leads to either lost data or infinite retry loops. The system distinguishes between transient failures (a downstream database briefly unreachable, worth retrying) and poison-pill failures (a malformed event that will never succeed no matter how many times it’s retried).
- Transient failures are retried with exponential backoff and jitter — doubling the wait time between attempts, with a small random offset added, to avoid many failed consumers retrying in lockstep and creating a synchronized thundering-herd spike against the recovering dependency.
- Poison-pill events — after a small, fixed number of retry attempts — are routed to a dead-letter queue rather than retried indefinitely, where they wait for either automatic reprocessing once a bug fix ships, or manual inspection if the payload itself is genuinely malformed.
- Backpressure is applied at the webhook gateway during extreme load by returning HTTP 429 to the sending e-commerce platform, which is documented to retry webhook delivery later — deliberately shedding load at the edge rather than accepting more events than the pipeline can safely absorb, which is a better outcome than accepting everything and failing unpredictably downstream.
A healthy dead-letter queue depth of zero, monitored continuously as described in Section 11.3, is itself a useful reliability signal — a growing dead-letter queue is often the earliest warning sign of a schema change on a partner platform’s side before it becomes a visible customer-facing problem.
6.3 Reconciliation Window Mechanics
A conversion record moves through a small number of well-defined states between the moment it’s first detected and the moment it’s finally locked for payout. Modeling this explicitly, rather than treating every conversion as instantly final, is what lets the system stay both fast (visible to dashboards immediately) and correct (protected from refunds and fraud that surface later).
PROVISIONAL
Order detected and attributed; visible on dashboards, not yet eligible for payout.
UNDER_REVIEW
Flagged by fraud detection or an unusually large order value; held for manual or automated review (up to a few days).
CONFIRMED
Passed the reconciliation window with no refund, chargeback, or fraud flag; eligible to enter a payout batch.
REVERSED
Refunded, cancelled, or confirmed fraudulent after being provisional or confirmed. Handled inside the dispute window.
PAID
Included in a completed payout batch; immutable from this point on except through a formal adjustment record.
Only CONFIRMED records are eligible for inclusion in a payout batch, and once a record reaches PAID it is never edited in place — any later-discovered issue (a very late refund, for instance) is handled by creating a new, linked adjustment record rather than mutating history, preserving the auditable trail described in Section 15.1’s event sourcing pattern.
Advantages, Disadvantages & Trade-offs
Advantages of this architecture
- Event-driven backbone decouples ingestion speed from processing complexity.
- Waterfall identity resolution keeps the system honest about confidence levels rather than pretending all matches are equal.
- Reconciliation windows absorb the messy, delayed reality of e-commerce data without blocking real-time dashboards.
- Pluggable attribution models let different brands use different rules without forking the codebase.
Disadvantages & costs
- Fire-and-forget click logging trades a small amount of durability for low latency — some clicks under broker failure can be lost.
- Multi-touch models are harder to explain to creators than simple last-click, which can create support burden.
- Maintaining normalizers for every e-commerce platform is ongoing engineering work as platforms change their webhook schemas.
- Reconciliation windows mean payouts can’t be instant — there is an inherent tension between “pay creators fast” and “pay creators correctly.”
7.1 Key Trade-off: Speed vs. Correctness in Payouts
This is the central tension of the whole system. Paying creators the moment a sale is detected builds trust and loyalty, but a meaningful fraction of e-commerce orders are refunded, cancelled, or later disputed within the first few weeks. Most mature platforms resolve this by paying quickly on a “provisional” basis for small creators while holding a portion in reserve, or by running weekly/bi-weekly payout cycles that align with the platform’s own reconciliation window.
“Would you rather over-count or under-count attribution, if you had to pick one systemic bias?” There’s no single right answer, but a thoughtful response weighs brand trust (over-counting inflates marketing spend and looks bad in board reporting) against creator trust (under-counting damages the relationships the whole business depends on), and typically lands on being conservative with automatic approval while making manual review fast and transparent.
7.2 Build vs. Buy
Not every company needs to build a system this elaborate from scratch. Third-party affiliate and creator-commerce platforms mentioned in Section 17 offer much of this functionality out of the box, and for many brands, buying is the correct engineering decision, not a compromise.
Build — large programs
Favors building when creator programs are large enough that vendor per-transaction fees become a major cost line item.
Buy — small/mid programs
Favors buying when engineering time is scarcer than vendor cost, and standard vendor features cover the use case.
Build — unusual rules
Favors building when the brand needs unusual attribution rules or commission structures a vendor simply doesn’t support out of the box.
Buy — standard models
Favors buying when the standard last-touch or linear models most vendors already handle well are sufficient.
Build — own the graph
Favors building when there’s a strategic need to own the full identity graph and journey data directly rather than through a vendor’s API.
Build — deep custom
Favors building when integration must reach deep into proprietary internal systems (inventory, CRM) that vendors don’t know how to handle.
This tutorial focuses on the “build” path because it’s the more instructive one architecturally, but a real engineering team evaluating this problem should treat the vendor landscape as a genuine, often more cost-effective alternative, and revisit the build decision as scale and customization needs grow.
Performance & Scalability
Click and impression volume for a single viral post can spike from near-zero to hundreds of thousands of events per hour within minutes. The system has to absorb bursty write load without falling over, while keeping read paths (dashboards) fast and separate.
8.1 Scaling the Ingestion Path
- Stateless redirect servers behind a load balancer, scaled horizontally by request rate, so a traffic spike is absorbed by adding more instances rather than by any single server working harder.
- Kafka as a shock absorber — writes to Kafka are cheap and append-only; even if downstream consumers (Identity Resolution, Attribution Engine) fall behind during a spike, events queue safely and get processed once consumers catch up, instead of being dropped or timing out the shopper’s request.
- Partitioning by visitor ID or creator ID so that related events for one shopper’s journey land on the same Kafka partition, preserving order without requiring a global lock.
8.2 Scaling the Read Path
Creator and brand dashboards should never query the raw event stream directly. Instead, materialized, pre-aggregated views (clicks per hour, conversions per campaign) are maintained separately, so a dashboard query is a fast lookup against a small aggregate table rather than a scan over billions of raw events.
8.3 Capacity Example
Suppose a mid-size platform runs 5,000 active creator campaigns, averaging 200 clicks/day each with occasional 50x spikes during viral moments. Baseline load is roughly 1M clicks/day (~12 events/sec average), but the system must be provisioned for burst capacity of several thousand events/sec sustained for short windows — a good candidate for Kafka’s partition-based horizontal scaling plus auto-scaling consumer groups rather than fixed-capacity servers.
“How would you handle a single post going viral and generating a huge, sudden spike in clicks?” Talk about the redirect tier being stateless and cheap to scale horizontally, Kafka absorbing the burst so consumers aren’t overwhelmed synchronously, and rate-limiting or CDN-level caching for the redirect lookup itself so the database backing tracking links isn’t hammered directly.
8.4 Algorithms and Data Structures That Matter Here
Consistent Hashing for Kafka Partition Assignment
When Kafka consumer instances scale up or down (auto-scaling in response to load), naive partition assignment would reshuffle nearly every partition-to-consumer mapping, causing a storm of unnecessary rebalancing. Consistent hashing minimizes the number of partitions that move when the consumer group size changes, so scaling the Attribution Engine from 4 to 6 instances only reassigns a small fraction of partitions instead of all of them.
Bloom Filters for Fast Duplicate Detection
Before hitting the database to check whether an order event has already been processed (Section 6.2), the Webhook Ingestion Gateway first checks an in-memory Bloom filter — a compact probabilistic data structure that can definitively say “this key was never seen” in constant time, or “this key was probably seen” (requiring a real database check to confirm). Since the vast majority of incoming webhooks are genuinely new, this cuts unnecessary idempotency-check database round trips dramatically.
public boolean isLikelyDuplicate(String idempotencyKey) {
// Fast path: Bloom filter says definitely not seen, skip the DB entirely
if (!bloomFilter.mightContain(idempotencyKey)) {
bloomFilter.put(idempotencyKey);
return false;
}
// Slow path: possible match, confirm against the source of truth
return processedEventRepo.existsByIdempotencyKey(idempotencyKey);
}Sliding Window Counters for Click Rate-Limiting
To blunt click-farm abuse (Section 10.3) without penalizing genuine bursts of real traffic, the redirect service tracks click counts per IP/device using a sliding window counter rather than a simple fixed window, which avoids the classic “double-burst at the window boundary” weakness of naive fixed-window rate limiters.
LSM-Tree-Backed Storage for the Event Store
Cassandra’s underlying Log-Structured Merge tree is well suited to this workload specifically because touchpoint and order events are almost always appended, rarely updated in place, and read primarily by recent time range per visitor — exactly the access pattern LSM trees are optimized for, trading slightly more expensive reads (which are mitigated by caching) for very cheap, sequential writes at massive scale.
8.5 Capacity Example, Continued
Extending the earlier example: at a sustained burst of 5,000 events/sec, a Kafka topic with 32 partitions and a consumer group of 16 instances each processing roughly 300 events/sec comfortably clears the load with headroom, while the Bloom-filter fast path keeps idempotency checks from becoming the bottleneck they would otherwise be at this volume.
High Availability & Reliability
9.1 What Must Never Go Down
The Link Redirect Service and Webhook Ingestion Gateway are the most availability-critical components: a shopper who clicks a broken link simply leaves, and a missed webhook can mean a real sale is never attributed at all. These are deployed across multiple availability zones with no single point of failure.
9.2 Replication and Failover
- Kafka topics are replicated across at least 3 brokers, so a broker failure doesn’t lose in-flight events (replication factor 3,
min.insync.replicas=2for critical topics likeorders). - PostgreSQL (holding creators, campaigns, and discount codes) runs with a synchronous standby for zero data loss on failover, plus asynchronous read replicas for scaling dashboard reads.
- The webhook gateway acknowledges receipt (returns HTTP 200) only after the event is durably published to Kafka, never before — this ensures the source e-commerce platform won’t consider the webhook “delivered” until it is safely queued internally.
9.3 Graceful Degradation
If the Attribution Engine or Fraud Detection service is temporarily unavailable, ingestion should continue unaffected — events simply queue in Kafka and get processed once the consumer recovers. This decoupling is precisely why an event-driven design was chosen over a synchronous request chain in Section 4.
This is like a post office that keeps accepting mail into its sorting facility even if the delivery trucks are temporarily stuck in traffic. The mail (events) is safely stored; delivery (processing) just happens a bit later. What the post office must never do is refuse to accept new mail because delivery is delayed.
“What’s your disaster recovery plan if the primary region goes down entirely?” A solid answer covers a warm standby in a secondary region with Kafka MirrorMaker (or equivalent) replicating topics cross-region, database backups with point-in-time recovery, and a documented RTO/RPO target — e.g. RPO under 5 minutes for order events, RTO under 30 minutes for full ingestion recovery.
9.4 Backup Strategy and Recovery Drills
Reliability guarantees are only as good as the last time they were actually tested. This system maintains three complementary layers of backup:
- Continuous WAL archiving for PostgreSQL, enabling point-in-time recovery to any second within the retention window, not just the last nightly snapshot.
- Cross-region Kafka topic mirroring, so a region-wide outage doesn’t strand in-flight click and order events that haven’t yet been consumed.
- Scheduled snapshot exports of the Cassandra event store to cold object storage, providing an independent recovery path even if the live cluster suffers correlated failures across multiple nodes at once.
Backups that have never been restored are only a hope, not a guarantee — quarterly recovery drills, where the team actually restores from backup into an isolated environment and verifies data integrity end-to-end, are treated as a non-negotiable operational practice rather than a nice-to-have.
Security
10.1 Webhook Authenticity
Every inbound webhook must be verified before it’s trusted, or an attacker could forge fake “order created” events to inflate a creator’s earnings. Platforms like Shopify sign webhooks with an HMAC-SHA256 signature using a shared secret; the gateway must recompute and compare this signature before accepting any payload.
public boolean verifyShopifySignature(String rawBody, String receivedHmac, String secret) {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] computedHash = mac.doFinal(rawBody.getBytes(StandardCharsets.UTF_8));
String computedHmac = Base64.getEncoder().encodeToString(computedHash);
// Constant-time comparison prevents timing attacks
return MessageDigest.isEqual(
computedHmac.getBytes(StandardCharsets.UTF_8),
receivedHmac.getBytes(StandardCharsets.UTF_8));
}10.2 PII Handling
Shopper emails and personal data are sensitive. The system should hash emails (e.g. SHA-256) at the point of ingestion for identity matching, avoid storing raw PII in analytics-facing stores, and apply field-level encryption at rest for anything that must remain in raw form for legal/refund purposes.
10.3 Fraud Vectors Specific to This Domain
Coupon code abuse
A creator (or a bad actor impersonating one) shares their own discount code widely on coupon-aggregator sites, generating “sales” with no genuine influence.
Click farms
Automated bot traffic clicks tracking links to inflate reported reach and engagement metrics used in campaign negotiations, even without completing purchases.
Self-purchase / commission farming
A creator or affiliate buys their own promoted product through their own link to collect a commission on money that never represented genuine new sales.
Mitigations include cross-referencing the shopper’s identity hash against the creator’s known identity, rate-limiting clicks per IP/device within short windows, and flagging orders whose shipping address matches a creator’s known address for manual review.
10.4 Access Control
Brands must only see their own campaign data; creators must only see their own earnings and never another creator’s commission rates. This is enforced through role-based access control at the API gateway layer plus row-level authorization checks in each service, never relying on the client to simply not ask for data it shouldn’t see.
“How would you detect a creator buying through their own link to inflate commissions?” Beyond email/address matching, mention behavioral signals: an unusually short time between click and purchase, a purchase pattern that doesn’t match the audience demographics the creator normally converts, or repeated small “test” orders — all of which feed the Fraud & Anomaly Detection service rather than being hard-coded rules alone.
10.5 Compliance and Regulatory Considerations
Because this system processes shopper data across multiple countries, it has to account for regional privacy law from the start rather than bolting it on later. In practice this means:
- Data minimization — only collecting the fields actually needed for attribution (hashed email, order value, product category), not a full customer profile.
- Right to erasure — supporting deletion requests under regimes like GDPR (EU) or the DPDP Act (India) by purging or irreversibly anonymizing a shopper’s identity graph on request, which is only tractable because identity data is centralized in the Identity Resolution Service rather than duplicated across every downstream store.
- Data residency — for brands operating in jurisdictions with data localization requirements, keeping that region’s raw event data within regional infrastructure rather than replicating it globally by default.
- Consent management — respecting the shopper’s cookie and tracking consent choices, and falling back gracefully to consent-independent signals like discount codes when tracking consent is declined, rather than silently ignoring the shopper’s choice.
Monitoring, Logging & Metrics
11.1 What to Measure
Edge health
Webhook success/failure rate per platform, redirect latency (p50/p95/p99), Kafka producer error rate.
Pipeline throughput
Consumer lag per topic, identity-match confidence distribution, attribution processing latency end-to-end.
Money signals
Attributed revenue per campaign, click-to-conversion rate, fraud-flag rate, payout accuracy (post-hoc audits vs. paid amounts).
SLO signals
Error budgets against SLOs, replication lag, dead-letter queue depth — the leading indicators of an incident brewing.
11.2 Distributed Tracing
Because one shopper journey touches five or more services (redirect, pixel, identity resolution, attribution, commission), a single trace ID generated at the click and propagated through every downstream event is essential for debugging “why wasn’t this sale attributed?” support tickets — without it, engineers would have to manually correlate logs across services by timestamp guesswork.
11.3 Alerting Philosophy
- Alert on consumer lag exceeding a threshold (e.g. Kafka topic lag > 100k messages) — a sign the Attribution Engine can’t keep up with load.
- Alert on a sudden drop in webhook volume from any single platform, which usually indicates a broken integration rather than genuinely fewer sales.
- Alert on anomalous spikes in the fraud-flag rate, which could indicate either a new attack pattern or an overly aggressive rule change.
Monitoring this system is like a hospital’s vital-signs monitor: individual metrics (heart rate, blood pressure) matter less in isolation than the combination and trend. A single slow webhook isn’t an emergency; a sustained rise in Kafka lag alongside a drop in successful attributions is the equivalent of a patient’s vitals trending badly together, and demands attention before things get critical.
11.3b Service Level Objectives and Error Budgets
Rather than chasing an undefined notion of “as reliable as possible,” each component is given an explicit Service Level Objective that reflects its actual criticality. The redirect service, being shopper-facing and availability-critical, might target 99.95% availability with p99 latency under 150ms; the Attribution Engine, which can tolerate brief processing delays without shoppers noticing, might target a looser 99.9% availability but a strict data-correctness SLO instead (zero tolerance for duplicate commission calculations). Tracking an error budget against each SLO — the small amount of allowed failure within a given period — gives the team an objective, pre-agreed basis for deciding when to prioritize reliability work over new features, rather than making that call reactively during an incident.
11.4 Structured Logging
Every log line emitted anywhere in the system is structured JSON, not free-form text, carrying a consistent set of fields — trace ID, service name, visitor ID (hashed), event type, and outcome — so logs from every service can be queried and correlated together in a centralized log aggregation platform without brittle text parsing. This matters enormously for the specific kind of investigation this domain demands: a support ticket that says “creator X says a sale from last Tuesday wasn’t attributed” requires reconstructing exactly what happened to one specific event across five or more services, and structured, trace-ID-linked logs are what makes that a five-minute query instead of a multi-hour manual investigation.
log.info("order_event_processed",
kv("trace_id", event.getTraceId()),
kv("platform", event.getPlatform()),
kv("order_id", event.getExternalOrderId()),
kv("match_strategy", matchResult.getStrategy()),
kv("match_confidence", matchResult.getConfidence()),
kv("attributed_creator_id", matchResult.getCreatorId()));Deployment & Cloud Architecture
12.1 Containerized Microservices on Kubernetes
Each core service (redirect, pixel collector, webhook gateway, identity resolution, attribution engine, commission service, payout service) is packaged as an independently deployable container, orchestrated by Kubernetes, allowing each to be scaled, deployed, and rolled back independently based on its own load characteristics.
12.2 CI/CD and Progressive Delivery
Given that this system touches real money, deployments follow a canary release pattern: a new version of the Attribution Engine, for example, is first routed a small percentage of traffic, its output compared against the previous version on shadow data, and only promoted to 100% once metrics confirm no regression in attribution accuracy.
12.3 Multi-Region Considerations
Creators and brands are global. Redirect latency matters most (shoppers are sensitive to slow link clicks), so the redirect tier is deployed in multiple regions close to major audience geographies, while the core data stores (PostgreSQL, Cassandra) are anchored in a primary region with cross-region read replicas and async backups for disaster recovery.
“Would you deploy the Attribution Engine in every region?” A nuanced answer: attribution requires a consistent, authoritative view of a shopper’s full journey, so it’s often better to keep it centralized (or per-region with careful data partitioning by shopper geography) rather than fully distributed, to avoid the complexity of merging conflicting attribution decisions made independently in different regions.
12.3b Infrastructure as Code
Every piece of infrastructure — Kubernetes manifests, Kafka topic configuration, database instance sizing, IAM roles — is defined declaratively in version-controlled code rather than created by hand through a cloud console. For a system handling real payouts, this matters beyond ordinary convenience: it means every infrastructure change is peer-reviewed, every environment (staging, production) is provably identical in configuration, and a full disaster recovery rebuild in a new region is a matter of re-applying known-good code rather than an undocumented, error-prone manual process reconstructed from memory during an actual crisis.
12.4 Cost Optimization
Running always-on infrastructure sized for viral-moment peak load, all day every day, is the single easiest way to overspend on this system. A few practical levers keep cost proportional to actual usage:
- Autoscaling based on Kafka consumer lag rather than raw CPU alone, since lag is a more direct signal of whether the Attribution Engine is actually falling behind, letting the platform scale down aggressively during quiet overnight hours.
- Tiered storage for the event store — recent, frequently-queried events stay on fast Cassandra nodes, while events older than the active reconciliation window are moved to much cheaper object storage, since they are rarely queried outside of audits or historical reporting.
- Spot/preemptible instances for the data warehouse’s batch ETL jobs, which are naturally fault-tolerant and can be retried, unlike the always-on redirect and webhook tiers which must run on reserved, stable capacity.
- Right-sizing Kafka replication — critical topics like
ordersjustify replication factor 3, but lower-stakes topics like raw impression counts (used only for directional analytics, not payouts) can run at replication factor 2 to save on storage cost.
Databases, Caching & Load Balancing
13.1 Choosing the Right Store for Each Job
Creators, campaigns, discount codes, contracts
Strongly relational, needs ACID guarantees for contract terms and financial rules that must never fork into inconsistent states.
Raw click/view/order events at scale
Massive write throughput, time-series-friendly, horizontally scalable without a single write bottleneck — ideal for the append-mostly event stream.
Identity graph, hot campaign stats
Sub-millisecond lookups for redirect-path decisions and dashboard caching where latency budgets are tightest.
Historical analytics, reporting
Columnar warehouse (Snowflake/BigQuery-class) optimized for large aggregate queries across billions of historical rows.
13.2 Why Not One Database for Everything
Using PostgreSQL alone for billions of raw click events would be like trying to store a river in a bathtub — it technically holds water, but it wasn’t built for constant, high-volume flow. Polyglot persistence means choosing the right container for each kind of data, rather than forcing one tool to do every job adequately but none of them well.
13.3 Caching Strategy
- Tracking link lookups are cached in Redis with a short TTL, since redirect latency is critical and link definitions rarely change after creation.
- Dashboard aggregates (clicks today, sales this week) are cached and refreshed on a rolling basis rather than computed live on every page load.
- Cache invalidation on campaign edits (e.g. a brand pauses a campaign) is handled by publishing an invalidation event rather than relying purely on TTL expiry, so paused campaigns stop tracking almost immediately.
13.4b Sharding the Relational Store as Brands Grow
PostgreSQL scales vertically quite far for the campaign/contract data described above, since that dataset is comparatively small even at large scale. But as the platform onboards very large numbers of brands, write-heavy operational tables (like discount code redemption tracking) benefit from sharding by brand_id, since almost every query naturally scopes to a single brand’s data — a brand’s dashboard never needs to join across another brand’s rows. This keeps each shard’s working set small enough to fit comfortably in memory, and avoids the far more complex alternative of sharding by a key that would force cross-shard joins for common queries.
13.4 Load Balancing
The redirect and webhook tiers sit behind Layer 7 load balancers that support health-check-based routing, so a pod failing health checks is automatically removed from rotation. Webhook traffic additionally uses request queuing with backpressure, since a burst of webhooks (e.g. after a flash sale) should be smoothed rather than causing cascading timeouts.
“Why Cassandra for events instead of just partitioning PostgreSQL?” Talk about write-heavy, append-mostly workload characteristics, Cassandra’s masterless architecture avoiding a single write bottleneck, and the fact that event data is rarely updated in place — all of which suit a wide-column store better than a relational database tuned for complex joins and strong consistency.
13.5 A Simplified Schema Example
To make the polyglot design concrete, here is a simplified view of how the same conceptual entity — a touchpoint — is modeled differently across two stores serving two different access patterns.
CREATE TABLE campaign (
id UUID PRIMARY KEY,
brand_id UUID NOT NULL REFERENCES brand(id),
attribution_model VARCHAR(32) NOT NULL DEFAULT 'LAST_TOUCH',
attribution_window_days INT NOT NULL DEFAULT 7,
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE discount_code (
code VARCHAR(32) PRIMARY KEY,
creator_id UUID NOT NULL REFERENCES creator(id),
campaign_id UUID NOT NULL REFERENCES campaign(id)
);CREATE TABLE touchpoints_by_visitor (
visitor_id TEXT,
occurred_at TIMESTAMP,
touchpoint_id UUID,
creator_id TEXT,
campaign_id TEXT,
event_type TEXT, -- CLICK, VIEW, ORDER
PRIMARY KEY (visitor_id, occurred_at, touchpoint_id)
) WITH CLUSTERING ORDER BY (occurred_at DESC);Partitioning the Cassandra table by visitor_id means every touchpoint for a given shopper’s journey lives on the same physical node, making the Attribution Engine’s “fetch this shopper’s full journey” query a single, fast, local read rather than a scatter-gather across the cluster — a schema decision made specifically to serve the access pattern described in Section 5.3, rather than a generic default.
APIs & Microservices
14.1 API Surface
The platform exposes a few distinct API surfaces to different consumers:
- Public tracking endpoints — the redirect and pixel URLs themselves, unauthenticated by design since any shopper’s browser must be able to hit them.
- Partner integration APIs — used by e-commerce platforms to register webhooks and by the platform to pull order data where webhooks aren’t available (e.g. Amazon’s reporting APIs).
- Dashboard APIs — authenticated REST/GraphQL APIs used by creator and brand-facing web apps to show campaign performance and earnings.
- Internal service-to-service APIs — mostly asynchronous via Kafka, with a small number of synchronous gRPC calls where a strict request/response is needed (e.g. Commission Service querying a creator’s current contract terms).
14.2 Sample Dashboard API Contract
@RestController
@RequestMapping("/api/v1/campaigns/{campaignId}/performance")
public class CampaignPerformanceController {
@GetMapping
@PreAuthorize("hasPermission(#campaignId, 'CAMPAIGN', 'READ')")
public CampaignPerformanceResponse getPerformance(
@PathVariable String campaignId,
@RequestParam(defaultValue = "LAST_TOUCH") AttributionModel model,
@RequestParam(required = false) String dateRange) {
return CampaignPerformanceResponse.builder()
.totalClicks(aggregationService.getClicks(campaignId, dateRange))
.totalConversions(aggregationService.getConversions(campaignId, model, dateRange))
.attributedRevenue(aggregationService.getRevenue(campaignId, model, dateRange))
.topCreators(aggregationService.getTopCreators(campaignId, dateRange))
.build();
}
}Notice the attribution model is a query parameter, not hard-coded — this lets a brand compare how their campaign looks under last-touch versus linear multi-touch without re-processing any underlying data, since the raw touchpoint journeys are stored once and models are applied at read/report time where feasible.
14.3 Why Microservices Here Specifically
This domain has genuinely different scaling and reliability profiles per component — the redirect service needs to be blazing fast and stateless, the attribution engine needs to be compute-heavy and can tolerate a few seconds of lag, the payout service needs strict transactional guarantees and can run on a much slower schedule. A monolith would force all of these to share the same deployment cadence and scaling rules, which doesn’t fit.
“Would you start this system as microservices from day one, or a modular monolith first?” A mature answer acknowledges that for a small team building an MVP, a modular monolith with clear internal boundaries (mirroring the same service boundaries described here) is often the pragmatic starting point, with extraction into real microservices happening once specific components (usually ingestion, due to load) actually need independent scaling.
14.4 API Versioning and Rate Limiting
Partner integration APIs and dashboard APIs are consumed by external code the platform doesn’t control — brand-side developers, third-party analytics tools — so breaking changes have a real blast radius. The platform follows a straightforward versioning discipline: the version is embedded in the URL path (/api/v1/...), old versions are supported for a documented deprecation period after a new version ships, and additive changes (new optional fields) never require a version bump, only breaking changes do.
Rate limiting on partner-facing APIs uses a token bucket algorithm per API key, which allows short bursts of legitimate activity (a brand’s dashboard loading several widgets at once) while still capping sustained abuse, and returns clear 429 Too Many Requests responses with a Retry-After header rather than silently dropping requests, so well-behaved client code can back off correctly.
Design Patterns & Anti-patterns
15.1 Patterns Used in This System
Event Sourcing (partial)
Raw click, view, and order events are stored immutably and never modified — the “current state” (e.g. a campaign’s total attributed revenue) is derived by processing the event log, which makes reprocessing under a new attribution model possible without losing history.
Strategy Pattern
Each attribution model (last-touch, linear, time-decay) is implemented as an interchangeable strategy behind a common interface, so the Attribution Engine can select the right algorithm per campaign configuration without conditional spaghetti code.
Saga Pattern
A payout run touches multiple services (Commission Service, Fraud Detection, Payment Provider) — a saga coordinates these steps with compensating actions (e.g. reversing a commission if a payment provider call fails) rather than relying on a single distributed transaction.
Circuit Breaker
Calls from the Webhook Ingestion Gateway out to third-party platform APIs (e.g. pulling extra order details from Amazon) are wrapped in circuit breakers, so a slow or failing third party doesn’t cascade into ingestion delays for every other platform.
public interface AttributionStrategy {
Map<String, BigDecimal> assignCredit(List<Touchpoint> journey, BigDecimal orderValue);
}
@Component("linear")
public class LinearAttributionStrategy implements AttributionStrategy {
@Override
public Map<String, BigDecimal> assignCredit(List<Touchpoint> journey, BigDecimal orderValue) {
BigDecimal sharePerTouch = orderValue.divide(
BigDecimal.valueOf(journey.size()), 2, RoundingMode.HALF_UP);
return journey.stream().collect(Collectors.toMap(
Touchpoint::getCreatorId, tp -> sharePerTouch, BigDecimal::add));
}
}15.2 Anti-patterns to Avoid
Synchronous chains for ingestion
Making the webhook handler call Identity Resolution and Attribution synchronously before returning 200 OK ties ingestion availability to the availability of much heavier downstream processing — one slow dependency can start dropping webhooks entirely.
Mutable “source of truth” aggregates
Storing only a running total of “creator earnings” and updating it in place, without an underlying immutable event log, makes it nearly impossible to audit a disputed payout or safely reprocess under a corrected attribution model.
Trusting client-provided attribution data
Accepting a referred_by=creator_x parameter directly from a browser without server-side verification invites trivial fraud — attribution signals must always be corroborated against server-recorded events, not client claims alone.
One giant “analytics” database for everything
As discussed in Section 13, forcing operational, event, and analytical workloads into a single database eventually causes lock contention and poor query performance across the board.
15.3 Anti-corruption Layer
Every external e-commerce platform’s data model leaks its own assumptions and quirks — Amazon’s report-based model is fundamentally different from Shopify’s real-time webhook model, and both differ from a custom storefront’s bespoke API. Rather than letting these external shapes bleed into the core domain model, each platform-specific normalizer (introduced in Section 5.2) acts as an anti-corruption layer: it is the only place in the system that understands Shopify’s or Amazon’s specific payload quirks, and everything past that boundary works exclusively with the platform’s own clean, internal OrderEvent schema. This means adding support for a brand-new e-commerce platform in the future only requires writing one new normalizer, never touching the Attribution Engine, Commission Service, or anything downstream.
Best Practices & Common Mistakes
Best practices
- Always design idempotent event handlers — assume every webhook can and will be delivered more than once.
- Keep raw event data immutable; compute derived state (like totals) separately and reproducibly.
- Make the attribution window and model explicit, configurable, and visible to brands, not a hidden implementation detail.
- Separate “attributed” from “paid” — a sale can be attributed immediately for visibility while payout waits for reconciliation.
- Instrument a trace ID at first touch and propagate it through every downstream event for debuggability.
Common mistakes
- Relying solely on cookies, which are increasingly blocked by browsers and privacy regulations, without a server-side fallback like discount codes.
- Hard-coding a single attribution model instead of treating it as configuration per campaign.
- Ignoring refunds and chargebacks in the payout pipeline, leading to creators being paid on sales that later reverse.
- Under-provisioning the redirect tier because “it’s just a redirect,” then getting caught out by a viral spike.
- Storing raw PII in the same tables used for high-volume analytics queries, creating unnecessary compliance exposure.
“What’s the single biggest source of attribution disputes in a system like this, in your experience?” A grounded answer: mismatched expectations between a brand’s chosen attribution model and what the creator assumed — which is why surfacing the model clearly in the dashboard, and providing a full audit trail per sale, matters as much as the algorithm itself.
16.1 Testing Strategy for a Money-Bearing Pipeline
Because incorrect output here means real financial harm, testing goes beyond typical unit coverage:
- Golden-dataset regression tests — a fixed set of realistic shopper journeys with known, hand-verified correct attribution outcomes under each supported model, run on every change to the Attribution Engine to catch silent regressions before they reach production.
- Shadow testing for model changes — as mentioned in Section 12.2, a new attribution model version processes live traffic in parallel with the current version, with outputs compared and diffed, before it’s ever allowed to affect a real payout.
- Chaos testing on the ingestion path — deliberately killing Kafka brokers or webhook gateway pods in a staging environment to verify the system degrades gracefully (queues events, doesn’t lose them) rather than silently dropping data, as promised in Section 9.3.
- Reconciliation audits — periodically re-deriving a sample of payout amounts entirely from the raw immutable event log and diffing against what was actually paid, as an end-to-end sanity check that the whole pipeline, not just individual components, is behaving correctly.
Real-World / Industry Examples
LTK (LIKEtoKNOW.it)
Built its business around “shoppable” creator posts with its own tracking links and app, giving it first-party visibility into clicks without relying purely on third-party cookies — a strong example of owning the redirect layer to control attribution quality.
Amazon Creator Connections / Associates
Uses unique affiliate tags appended to Amazon product URLs; because Amazon controls the entire checkout, it can attribute with very high confidence within its own ecosystem, though it does not extend to purchases made outside Amazon.
Shopify Collabs
Integrates discount codes and tracking links directly at the storefront platform level, giving brands running on Shopify native access to creator-attributed order data without needing a fully separate attribution vendor.
Impact.com / Partnerize
General-purpose partnership management platforms that extended traditional affiliate tracking (cookies, pixels) to support influencer-specific use cases like flat-fee content deals alongside performance-based commissions.
TikTok Shop
Combines content, checkout, and fulfillment inside a single app experience, which mirrors LTK’s strategy of owning the funnel — when the platform controls both the video view and the purchase, attribution stops being a cross-system reconciliation problem and becomes a straightforward internal join.
Refersion & similar affiliate-tech vendors
Offer brands plug-in tracking scripts and dashboards specifically tuned for creator programs, illustrating the vendor layer that smaller brands rely on instead of building an attribution platform like this one in-house.
17.1 A Lesson from the Industry’s Own Growing Pains
Across the affiliate and influencer-tech industry, one recurring lesson shows up repeatedly in public post-mortems and vendor documentation: platforms that launched with only cookie-based tracking consistently found themselves undercounting influencer-driven sales as browsers tightened third-party cookie policies through the late 2010s and early 2020s, forcing a scramble to retrofit discount-code and server-side tracking after the fact. The platforms that fared best were the ones that treated cookies as one signal among several from the start, rather than as the sole source of truth — which is precisely why this tutorial’s identity resolution waterfall (Section 5.3) is designed to degrade gracefully across multiple signal types rather than depending on any single one.
A common thread across all of these: the platforms with the strongest attribution confidence are the ones that own more of the funnel directly (LTK’s own app, Amazon’s own checkout, TikTok Shop’s integrated commerce) — third-party attribution across arbitrary brand websites is inherently noisier, which is exactly why discount codes remain the most durable, brand-agnostic identifier even in an increasingly cookie-restricted world. This is also why many brands run a hybrid approach in practice: native platform attribution (Amazon, TikTok Shop) for sales that happen inside those ecosystems, plus a general-purpose attribution platform like the one designed in this tutorial for everything that happens on the brand’s own website and beyond.
FAQ, Summary & Key Takeaways
Why not just use cookies for everything?
Cookies are increasingly blocked by browsers (Safari’s ITP, Firefox’s ETP) and third-party cookies are being phased out industry-wide. They also fail entirely for cross-device journeys — a shopper who watches content on their phone and buys on their laptop leaves no cookie trail at all. Discount codes and server-side identity resolution are more durable complements.
How do you handle a shopper who was influenced by two different creators?
The Identity Resolution Service builds an ordered list of all touchpoints within the attribution window, and the chosen attribution model (linear, time-decay, position-based) determines how credit is split between them, rather than forcing a single winner-take-all decision.
What happens if an order is refunded after the creator has already been paid?
This is why reconciliation windows and payout batching exist — most systems either hold a small reserve against future refunds or net the refund against the creator’s next payout cycle, with clear terms disclosed in the creator contract upfront.
Is real-time attribution actually necessary?
For dashboards and creator motivation, near-real-time (seconds to low minutes) is generally sufficient and far cheaper to build reliably than hard real-time guarantees; true real-time is rarely a genuine business requirement here, only a perceived one worth challenging during design.
How would this design change for a much smaller brand with only a handful of creators?
Most of the architectural principles still apply, but the implementation can shrink dramatically — a small brand can likely run identity resolution, attribution, and commission calculation as modules within a single well-structured service rather than fully separate microservices, deferring the full split described in Section 4 until genuine independent scaling needs appear.
How does this system handle privacy regulations like GDPR or India’s DPDP Act?
By minimizing raw PII storage (hashing emails at ingestion, as described in Section 10.2), supporting data deletion requests by purging or anonymizing a shopper’s identity graph on demand, and keeping a clear record of what data is collected and why — all of which are easier to guarantee when PII handling is centralized in the Identity Resolution Service rather than scattered across every component.
What’s the very first component to build if starting from scratch?
The Link Redirect Service plus a basic click event log, since discount-code and click-based attribution alone (without the full multi-touch identity resolution waterfall) already delivers most of the business value a brand needs, and every later component builds naturally on top of that foundational event stream.
How is this different from general-purpose ad attribution used by platforms like Google or Meta Ads?
Ad-platform attribution mostly operates within a single, closed ecosystem the platform fully controls (its own ads, its own click data), whereas influencer attribution must reconcile signals across many independent, uncooperative e-commerce systems that were never built with attribution in mind — which is exactly why identity resolution (Section 5.3) is a much larger part of this system than it typically needs to be in walled-garden ad platforms.
Could machine learning replace the rule-based identity resolution waterfall entirely?
A learned model can complement the waterfall well, particularly for the lowest-confidence probabilistic tier, by learning subtler correlations than hand-written rules can capture. But replacing the high-confidence tiers (exact click ID, discount code) with a model would trade a deterministic, easily auditable match for a probabilistic one in cases where certainty is actually achievable — a poor trade-off for a system whose output determines real payouts and must be explainable during disputes.
Key Takeaways
- Attribution for influencer commerce is fundamentally an identity-resolution problem layered under an event-processing system — the algorithms matter less than the quality of the underlying signals feeding them.
- Decoupling fast, always-available ingestion from heavier, reprocessable attribution logic via an event backbone is the single most important architectural decision in this domain.
- Discount codes remain the most durable cross-device, cookie-independent attribution signal available today.
- Idempotency and immutable event logs aren’t optional niceties here — they’re what makes audited, disputed payouts survivable.
- Different brands need different attribution models; treat the model as configuration, not as a fixed algorithm baked into the pipeline.
Pulled together, the system described across this tutorial is really three smaller systems working in concert: a high-throughput, always-available event collector at the edges; a careful, auditable reconciliation engine in the middle that turns fragmented signals into trustworthy conversion records; and a strict, ledger-like payout system on the far end that treats real money with the seriousness it deserves. Getting any one of these three right in isolation is a moderately interesting distributed-systems exercise. Getting all three right together, while keeping creators paid accurately and brands able to trust the numbers, is what makes influencer attribution a genuinely hard and genuinely interesting system to design.