cryptographic click tokens succeeds.
// src/attribution/clickTracker.ts
import { createHash } from 'crypto';
import { redis } from '../infrastructure/redis';
export interface ClickPayload {
referrerId: string;
campaignId: string;
platform: 'web' | 'ios' | 'android';
timestamp: number;
}
export async function generateAttributionLink(payload: ClickPayload): Promise<string> {
const token = createHash('sha256')
.update(`${payload.referrerId}:${payload.campaignId}:${payload.timestamp}`)
.digest('hex')
.slice(0, 24);
const ttl = 7 * 24 * 60 * 60; // 7 days
await redis.setex(`attr:${token}`, ttl, JSON.stringify(payload));
const baseUrl = process.env.APP_URL;
return `${baseUrl}/invite/${token}?ref=${payload.referrerId}&camp=${payload.campaignId}`;
}
export async function resolveAttribution(token: string): Promise<ClickPayload | null> {
const raw = await redis.get(`attr:${token}`);
if (!raw) return null;
return JSON.parse(raw) as ClickPayload;
}
The click token is stored in Redis with a bounded TTL to prevent unbounded growth. Deferred linking services (Branch, Adjust, or custom native handlers) exchange the token for attribution on first app open.
Step 2: Idempotent Reward Distribution
Reward engines must guarantee exactly-once delivery. Network retries, duplicate webhook deliveries, and concurrent signups will cause double-payouts without idempotency keys and state tracking.
// src/rewards/rewardEngine.ts
import { db } from '../infrastructure/db';
import { EventEmitter } from 'events';
export interface RewardEvent {
eventId: string; // idempotency key
referrerId: string;
refereeId: string;
rewardType: 'credit' | 'discount' | 'feature_unlock';
amount: number;
triggeredAt: number;
}
const rewardEmitter = new EventEmitter();
rewardEmitter.setMaxListeners(100);
export async function processReferralReward(event: RewardEvent): Promise<void> {
const exists = await db.query(
'SELECT 1 FROM reward_logs WHERE event_id = $1',
[event.eventId]
);
if (exists.rowCount && exists.rowCount > 0) {
return; // idempotent skip
}
await db.transaction(async (tx) => {
await tx.query(
'INSERT INTO reward_logs (event_id, referrer_id, referee_id, reward_type, amount, triggered_at) VALUES ($1, $2, $3, $4, $5, $6)',
[event.eventId, event.referrerId, event.refereeId, event.rewardType, event.amount, event.triggeredAt]
);
await tx.query(
'UPDATE user_balances SET credits = credits + $1 WHERE user_id = $2',
[event.amount, event.referrerId]
);
});
rewardEmitter.emit('reward.distributed', event);
}
The event_id acts as an idempotency key. Database-level deduplication prevents race conditions. Transactional balance updates ensure consistency. The event emitter integrates with downstream analytics or notification services.
Step 3: Anti-Abuse & Velocity Controls
Viral loops are high-value targets for bot networks, referral rings, and automated farms. Velocity checks, device fingerprinting, and behavioral scoring must run before reward distribution.
// src/antiabuse/velocityGuard.ts
import { redis } from '../infrastructure/redis';
export interface FraudCheckInput {
refereeId: string;
referrerId: string;
ip: string;
deviceId: string;
timestamp: number;
}
export async function evaluateReferralRisk(input: FraudCheckInput): Promise<{ blocked: boolean; score: number }> {
const window = 24 * 60 * 60; // 24h
const key = `ref:velocity:${input.referrerId}`;
const count = await redis.zcount(key, input.timestamp - window, input.timestamp);
const score = Math.min(count * 0.25, 1.0); // linear risk scaling
if (count >= 8) {
await redis.zadd(key, input.timestamp, `${input.refereeId}:${input.timestamp}`);
return { blocked: true, score };
}
// Cross-device/IP correlation placeholder
const ipKey = `ref:ip:${input.ip}`;
const ipCount = await redis.get(ipKey);
const ipRisk = ipCount && parseInt(ipCount) > 5 ? 0.4 : 0;
const totalScore = Math.min(score + ipRisk, 1.0);
const blocked = totalScore > 0.85;
if (!blocked) {
await redis.zadd(key, input.timestamp, `${input.refereeId}:${input.timestamp}`);
await redis.incr(ipKey);
await redis.expire(ipKey, window);
}
return { blocked, score: totalScore };
}
Velocity limits prevent rapid-fire referrals. IP/device correlation catches shared infrastructure. The scoring model is configurable and should be replaced with ML-based fraud detection at scale.
Step 4: Architecture Decisions & Rationale
- Event-Driven Backbone: Kafka or Redis Streams decouple attribution capture from reward distribution. Enables replayability, audit trails, and independent scaling.
- Stateless Reward Service: Keeps business logic separate from storage. Scales horizontally; idempotency keys handle retries.
- Graph Attribution: PostgreSQL with
ltree or Neo4j tracks multi-hop referrals. Prevents circular attribution and enables LTV attribution across 2β3 degrees.
- Feature Flags: LaunchDarkly or custom flag service controls reward thresholds, attribution windows, and A/B test variants without deployments.
- Observability: Distributed tracing (OpenTelemetry) links click β install β signup β activation β reward. Metrics: conversion rate, attribution latency, reward distribution success rate, fraud block rate.
Pitfall Guide
1. Ignoring Attribution Windows
Attribution expires if the window is too short or unbounded if too long. 7β14 days aligns with typical install-to-signup latency. Longer windows inflate false positives; shorter windows discard legitimate conversions. Always parameterize the window and tie it to cohort decay data.
2. Missing Idempotency on Reward Distribution
Network retries, webhook duplicates, and concurrent signup handlers cause double-payouts. Without event_id deduplication and transactional balance updates, reward economics collapse. Implement exactly-once semantics at the database level, not just the application layer.
3. Hardcoded Referral Links
Static URLs break across platforms, fail deferred deep linking, and cannot carry cryptographic tokens. Use dynamic link generators with TTL-bound Redis storage. Map tokens to attribution payloads at first open, not at click time.
4. No Velocity or Behavioral Controls
Referral rings and bot networks exploit unlimited rewards. Implement sliding-window rate limits, IP/device correlation, and activation thresholds (e.g., reward only after first meaningful action). Log blocked events for fraud analysis.
5. Over-Rewarding Early vs Late Cohorts
Fixed reward amounts ignore LTV decay. Early adopters convert faster and drive secondary loops; late cohorts require higher incentives. Implement tiered rewards based on referral depth, activation quality, or cohort LTV forecasts.
6. Breaking UX Friction
Requiring manual code entry, email verification, or multi-step confirmation kills conversion. Use auto-apply tokens, deferred deep linking, and one-tap activation. Measure drop-off at each step; eliminate friction that isn't fraud-mitigation critical.
7. Not Tracking Cohort Decay
Virality compounds only if referred users trigger secondary loops. Track k-factor (invites per user Γ conversion rate) across 7/14/30-day windows. If k < 1, the loop is linear, not exponential. Adjust reward timing, activation thresholds, or sharing prompts to push k above 1.
Production Best Practices:
- Anchor rewards to activation events, not registration.
- Use cryptographic tokens with bounded TTLs.
- Implement exactly-once reward delivery with database-level idempotency.
- Instrument every hop with OpenTelemetry; track attribution latency.
- Run continuous A/B tests on reward amounts, attribution windows, and sharing CTAs.
- Separate fraud scoring from reward distribution; allow manual override paths.
- Monitor k-factor, CAC payback, and referred cohort churn weekly.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| B2B SaaS with long sales cycles | Activation-gated rewards + 14-day attribution window | Signups don't equal value; reward after first workspace invite or API call | Higher per-reward cost, lower fraud, better LTV alignment |
| Consumer mobile app | Deferred deep linking + instant credit on install | Friction kills conversion; instant reward drives rapid sharing | Low engineering overhead, moderate fraud risk, high volume |
| Two-sided marketplace | Tiered rewards based on both sides completing transaction | Prevents fake listings or ghost buyers; ensures loop quality | Higher operational complexity, strong margin protection |
| Web-only platform | Email/URL token + cookie fallback + 7-day window | No app install; relies on browser persistence and UTM tracking | Low infra cost, attribution drift risk, requires strict UTM hygiene |
| High-fraud vertical (gaming/crypto) | ML fraud scoring + device fingerprinting + reward escrow | Bot networks target reward pools; escrow prevents instant cash-out | High infra cost, strong abuse mitigation, slower reward release |
Configuration Template
# viral-loop-config.yaml
attribution:
window_days: 10
token_ttl_seconds: 604800
deep_link_provider: branch # or custom
cross_device_fallback: true
rewards:
type: credit
base_amount: 5
activation_threshold: 1 # minimum actions before payout
max_per_user_per_day: 5
idempotency_enabled: true
escrow_days: 0 # set >0 for fraud-heavy verticals
anti_abuse:
velocity_window_hours: 24
max_referrals_per_window: 8
ip_correlation_limit: 5
device_fingerprinting: true
fraud_score_threshold: 0.85
analytics:
track_k_factor: true
cohort_intervals_days: [7, 14, 30]
attribution_latency_alert_ms: 1200
fraud_event_sink: kafka://growth-events.fraud
feature_flags:
reward_tier_v2: false
attribution_window_extended: false
deferred_deep_link_enabled: true
Quick Start Guide
- Generate dynamic invite links: Integrate the
generateAttributionLink function into your sharing UI. Store tokens in Redis with a 7-day TTL.
- Hook deferred deep linking: On first app/web open, extract the token, call
resolveAttribution, and attach the payload to the signup event.
- Wire idempotent rewards: Emit a
signup.completed event containing event_id, referrerId, and refereeId. Route to processReferralReward with database-level deduplication.
- Apply velocity guard: Before reward distribution, pass referral metadata to
evaluateReferralRisk. Block if blocked: true; route to fraud sink if score > 0.6.
- Instrument & validate: Deploy OpenTelemetry spans across click β install β signup β reward. Verify attribution accuracy > 90%, fraud block rate < 5%, and reward distribution success rate > 99% in staging before production rollout.