lly.
4. Schema Registry: To prevent analytics breakage, all growth events must adhere to a strict schema validated at the ingestion point.
Implementation: TypeScript Growth Engine
The following implementation demonstrates a referral growth loop engine. It handles link creation, attribution, fraud detection heuristics, and reward distribution.
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
import { Redis } from 'ioredis';
import { Database } from './db';
// Schema Definition for Growth Events
const ReferralEventSchema = z.object({
eventId: z.string().uuid(),
timestamp: z.number(),
referrerId: z.string().uuid(),
refereeId: z.string().uuid(),
referralCode: z.string().min(6).max(12),
source: z.enum(['web', 'mobile', 'api']),
metadata: z.record(z.unknown()).optional(),
});
type ReferralEvent = z.infer<typeof ReferralEventSchema>;
// Growth Engine Configuration
interface GrowthConfig {
rewardAmount: number;
currency: string;
fraudThreshold: number; // Max referrals per IP/hour
attributionWindowHours: number;
}
export class GrowthOrchestrationService {
constructor(
private db: Database,
private redis: Redis,
private config: GrowthConfig
) {}
/**
* Generates a unique referral code and stores mapping.
* Ensures idempotency via Redis lock.
*/
async createReferralLink(userId: string): Promise<string> {
const lockKey = `lock:referral:create:${userId}`;
const acquired = await this.redis.set(lockKey, '1', 'EX', 5, 'NX');
if (!acquired) {
throw new Error('Referral generation in progress or throttled.');
}
try {
const code = this.generateCode();
await this.db.referralCodes.upsert({
userId,
code,
createdAt: Date.now(),
status: 'active'
});
return code;
} finally {
await this.redis.del(lockKey);
}
}
/**
* Processes a conversion event from a referred user.
* Validates attribution and distributes rewards atomically.
*/
async processConversion(event: ReferralEvent): Promise<void> {
// 1. Validate Schema
const validatedEvent = ReferralEventSchema.parse(event);
// 2. Idempotency Check
const processedKey = `processed:conversion:${validatedEvent.eventId}`;
const isProcessed = await this.redis.get(processedKey);
if (isProcessed) return;
// 3. Fraud Detection Heuristic
await this.checkFraudSignals(validatedEvent);
// 4. Transactional Reward Distribution
await this.db.transaction(async (tx) => {
// Verify Referral Code validity and attribution window
const codeRecord = await tx.referralCodes.findUnique({
where: { code: validatedEvent.referralCode }
});
if (!codeRecord || this.isExpired(codeRecord.createdAt)) {
throw new Error('Invalid or expired referral code.');
}
// Credit Referrer
await tx.wallets.update({
where: { userId: codeRecord.userId },
data: { balance: { increment: this.config.rewardAmount } }
});
// Credit Referee (Optional: welcome bonus)
await tx.wallets.update({
where: { userId: validatedEvent.refereeId },
data: { balance: { increment: Math.floor(this.config.rewardAmount * 0.5) } }
});
// Log Growth Event for Analytics
await tx.growthLogs.create({
data: {
eventId: validatedEvent.eventId,
type: 'referral_conversion',
referrerId: codeRecord.userId,
refereeId: validatedEvent.refereeId,
rewardDistributed: this.config.rewardAmount,
timestamp: validatedEvent.timestamp
}
});
});
// Mark as processed
await this.redis.set(processedKey, '1', 'EX', 86400); // 24h retention
}
private async checkFraudSignals(event: ReferralEvent): Promise<void> {
const ipKey = `fraud:ip:${event.metadata?.ip}`;
const count = await this.redis.incr(ipKey);
await this.redis.expire(ipKey, 3600); // 1 hour window
if (count > this.config.fraudThreshold) {
throw new Error('Fraud threshold exceeded.');
}
}
private isExpired(createdAt: number): boolean {
const now = Date.now();
const windowMs = this.config.attributionWindowHours * 60 * 60 * 1000;
return (now - createdAt) > windowMs;
}
private generateCode(): string {
return uuidv4().replace(/-/g, '').substring(0, 8).toUpperCase();
}
}
Key Technical Decisions:
- Zod Validation: Enforces contract stability. If the client sends malformed data, the engine rejects it immediately, protecting downstream analytics.
- Redis Idempotency: Prevents duplicate rewards caused by network retries or client-side double-clicks.
- Database Transactions: Ensures that wallet updates and log creation are atomic. If the log fails, the money is not credited, preventing financial discrepancies.
- Fraud Heuristics: Basic IP-based throttling is implemented at the edge of the logic to block abuse before database writes occur.
Pitfall Guide
Engineering growth systems introduces specific risks distinct from standard feature development.
-
Schema Drift in Analytics:
- Mistake: Frontend teams change event names or payload structures without updating the backend schema.
- Impact: Analytics dashboards break; historical data becomes incomparable.
- Best Practice: Implement a centralized schema registry. Use code generation to share types between frontend SDKs and backend services. CI/CD pipelines must validate events against the registry.
-
Ignoring Attribution Windows:
- Mistake: Assigning credit only to the last click or ignoring time decay.
- Impact: Over-crediting top-of-funnel channels; under-investing in nurturing loops.
- Best Practice: Implement multi-touch attribution models in your data warehouse. Configure configurable attribution windows in the growth engine to test different models.
-
Reward Abuse and Sybil Attacks:
- Mistake: Distributing rewards without verification of genuine user activity.
- Impact: Financial loss; influx of low-quality users who churn immediately.
- Best Practice: Require "activation" events before rewards are finalized. Implement device fingerprinting, phone verification, or behavioral analysis for high-value rewards.
-
Hardcoding Growth Logic:
- Mistake: Embedding referral logic or experiment variants directly in UI components.
- Impact: Changes require full app releases; inability to run A/B tests; code bloat.
- Best Practice: Externalize growth rules to a configuration service. Use feature flags to control visibility and logic paths. The UI should query the growth service for the current state.
-
Latency in Feedback Loops:
- Mistake: Relying on batch processing for growth metrics.
- Impact: Inability to react to viral spikes or detect fraud in real-time.
- Best Practice: Use real-time event streaming (e.g., Kafka, Kinesis) for growth events. Process critical growth logic synchronously or with low-latency queues.
-
Breaking Core Product Stability:
- Mistake: Growth experiments introduce latency or errors in the primary user flow.
- Impact: Degraded user experience; increased churn.
- Best Practice: Isolate growth services. Growth operations should be non-blocking. If the growth service fails, the core product must function normally. Implement circuit breakers.
-
Privacy and Compliance Violations:
- Mistake: Tracking PII in growth events without consent or masking.
- Impact: GDPR/CCPA fines; loss of user trust.
- Best Practice: Hash PII at the source. Implement data retention policies automatically. Ensure event schemas exclude sensitive fields by default.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Pre-Product/Market Fit | Manual Growth & Concierge MVP | Speed of learning; low overhead; direct user feedback. | Low |
| Post-PMF, Early Scale | Engineered Referral Loops | Leverage existing users for acquisition; sub-linear CAC. | Medium (Dev time) |
| High Fraud Risk Vertical | Strict Verification + Delayed Rewards | Protect margins; ensure user quality over quantity. | High (Verification costs) |
| B2B Enterprise Sales | Account-Based Growth + API Integrations | Align with sales cycles; focus on integrations and partnerships. | High (Sales/Eng resources) |
| Regulated Industry | Compliance-First Growth | Avoid fines; build trust; data residency requirements. | Medium (Compliance overhead) |
Configuration Template
Use this template to define growth experiments and rules in a declarative format. This can be stored in a database or config management system.
# growth-experiments.yaml
version: "1.0"
experiments:
- id: "referral_boost_q3"
name: "Referral Reward Increase"
status: "running"
start_date: "2024-07-01"
end_date: "2024-09-30"
target_segment: "users_with_10_plus_activity"
variants:
- id: "control"
weight: 50
reward_amount: 10
currency: "credits"
- id: "variant_a"
weight: 50
reward_amount: 25
currency: "credits"
condition: "referee_completes_onboarding"
metrics:
primary: "referral_conversion_rate"
guardrail: "reward_cost_per_acquisition"
fraud_rules:
max_referrals_per_ip: 5
min_session_duration_seconds: 30
Quick Start Guide
- Initialize SDK: Install the growth SDK in your client application.
npm install @codcompass/growth-sdk
- Configure Client: Initialize the SDK with your environment keys and schema version.
import { GrowthClient } from '@codcompass/growth-sdk';
const growth = new GrowthClient({
apiKey: process.env.GROWTH_API_KEY,
schemaVersion: 'v1.2',
environment: 'production'
});
- Track Events: Emit events for key user actions.
growth.track('user_signed_up', {
source: 'referral',
referrerCode: 'ABC123'
});
- Verify Integration: Check the real-time dashboard or logs to confirm events are arriving and schemas are valid.
- Launch Experiment: Use the configuration template to define your first experiment and activate it via the feature flag dashboard. Monitor the guardrail metrics closely for the first 48 hours.