Referral Program Implementation for ChatGPT Apps: Complete Growth Guide
Referral programs represent the most cost-effective user acquisition channel for ChatGPT apps, with customer acquisition costs 5-10x lower than paid advertising and retention rates 37% higher than other channels. With 800 million weekly ChatGPT users, a well-designed referral program can achieve viral coefficients (K-factor) above 1.0, enabling exponential growth without proportional marketing spend.
This guide provides production-ready implementation strategies for building referral systems that scale from 100 to 100,000+ users while preventing fraud, optimizing conversion funnels, and maximizing viral growth. We'll cover referral tracking architecture, reward systems, fraud prevention, viral optimization techniques, and analytics frameworks that power successful ChatGPT app referral programs.
Whether you're launching your first referral program or optimizing an existing system, these strategies and code examples will help you build a growth engine that compounds over time. Learn how to implement attribution tracking, automate reward distribution, prevent referral fraud, and optimize your K-factor for sustainable viral growth.
Referral Tracking Architecture
Effective referral tracking requires robust attribution systems that work across devices, browsers, and sessions while maintaining user privacy and GDPR compliance.
Multi-Channel Attribution System:
// referral-tracker.ts - Production-grade referral tracking system
import { v4 as uuidv4 } from 'uuid';
import crypto from 'crypto';
interface ReferralCode {
code: string;
userId: string;
createdAt: Date;
expiresAt: Date | null;
campaignId?: string;
metadata: Record<string, any>;
}
interface ReferralClick {
id: string;
referralCode: string;
clickedAt: Date;
ipAddress: string;
userAgent: string;
referrer: string;
utmSource?: string;
utmMedium?: string;
utmCampaign?: string;
fingerprint: string;
}
interface ReferralConversion {
id: string;
referralCode: string;
referrerId: string;
referredUserId: string;
convertedAt: Date;
attributionMethod: 'cookie' | 'url' | 'fingerprint' | 'manual';
rewardStatus: 'pending' | 'credited' | 'failed';
rewardAmount?: number;
}
class ReferralTracker {
private readonly cookieName = 'ref_code';
private readonly cookieMaxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
private readonly codeLength = 8;
/**
* Generate unique referral code for user
*/
generateReferralCode(userId: string, customCode?: string): string {
if (customCode) {
// Validate custom code (alphanumeric, 6-12 chars)
if (!/^[a-zA-Z0-9]{6,12}$/.test(customCode)) {
throw new Error('Invalid custom code format');
}
return customCode.toUpperCase();
}
// Generate cryptographically secure random code
const bytes = crypto.randomBytes(this.codeLength);
const code = bytes.toString('base64')
.replace(/[^a-zA-Z0-9]/g, '')
.substring(0, this.codeLength)
.toUpperCase();
return code;
}
/**
* Track referral click with device fingerprinting
*/
async trackClick(
referralCode: string,
request: {
ip: string;
userAgent: string;
referrer: string;
query: Record<string, string>;
}
): Promise<ReferralClick> {
const fingerprint = this.generateFingerprint(
request.ip,
request.userAgent
);
const click: ReferralClick = {
id: uuidv4(),
referralCode,
clickedAt: new Date(),
ipAddress: this.hashIP(request.ip), // Hash for privacy
userAgent: request.userAgent,
referrer: request.referrer,
utmSource: request.query.utm_source,
utmMedium: request.query.utm_medium,
utmCampaign: request.query.utm_campaign,
fingerprint,
};
// Store in database
await this.saveClick(click);
return click;
}
/**
* Generate device fingerprint for attribution
*/
private generateFingerprint(ip: string, userAgent: string): string {
const hash = crypto.createHash('sha256');
hash.update(`${ip}:${userAgent}`);
return hash.digest('hex').substring(0, 16);
}
/**
* Hash IP address for privacy compliance
*/
private hashIP(ip: string): string {
const hash = crypto.createHash('sha256');
hash.update(ip);
return hash.digest('hex').substring(0, 12);
}
/**
* Attribute conversion to referrer with fallback methods
*/
async attributeConversion(
newUserId: string,
request: {
cookie?: string;
ip: string;
userAgent: string;
urlCode?: string;
}
): Promise<ReferralConversion | null> {
let referralCode: string | null = null;
let attributionMethod: ReferralConversion['attributionMethod'] = 'cookie';
// Method 1: Cookie-based attribution (most reliable)
if (request.cookie) {
referralCode = request.cookie;
attributionMethod = 'cookie';
}
// Method 2: URL parameter attribution
else if (request.urlCode) {
referralCode = request.urlCode;
attributionMethod = 'url';
}
// Method 3: Fingerprint-based attribution (fallback)
else {
const fingerprint = this.generateFingerprint(
request.ip,
request.userAgent
);
const recentClick = await this.findClickByFingerprint(
fingerprint,
24 * 60 * 60 * 1000 // 24-hour window
);
if (recentClick) {
referralCode = recentClick.referralCode;
attributionMethod = 'fingerprint';
}
}
if (!referralCode) {
return null; // No attribution possible
}
// Get referrer user ID from code
const referrerCode = await this.getReferralCode(referralCode);
if (!referrerCode || this.isExpired(referrerCode)) {
return null;
}
// Prevent self-referrals
if (referrerCode.userId === newUserId) {
console.warn(`Self-referral attempt: ${newUserId}`);
return null;
}
const conversion: ReferralConversion = {
id: uuidv4(),
referralCode,
referrerId: referrerCode.userId,
referredUserId: newUserId,
convertedAt: new Date(),
attributionMethod,
rewardStatus: 'pending',
};
await this.saveConversion(conversion);
return conversion;
}
private isExpired(code: ReferralCode): boolean {
if (!code.expiresAt) return false;
return new Date() > code.expiresAt;
}
// Database methods (implement with your DB layer)
private async saveClick(click: ReferralClick): Promise<void> {
// INSERT INTO referral_clicks ...
}
private async findClickByFingerprint(
fingerprint: string,
maxAge: number
): Promise<ReferralClick | null> {
// SELECT * FROM referral_clicks WHERE fingerprint = ? AND clicked_at > NOW() - ?
return null;
}
private async getReferralCode(code: string): Promise<ReferralCode | null> {
// SELECT * FROM referral_codes WHERE code = ?
return null;
}
private async saveConversion(conversion: ReferralConversion): Promise<void> {
// INSERT INTO referral_conversions ...
}
}
export { ReferralTracker, ReferralCode, ReferralClick, ReferralConversion };
Cookie Management & Cross-Domain Tracking:
// cookie-manager.ts - Referral cookie handling
interface CookieOptions {
domain?: string;
path?: string;
maxAge?: number;
secure?: boolean;
sameSite?: 'strict' | 'lax' | 'none';
}
class ReferralCookieManager {
private readonly cookieName = 'ref_code';
private readonly defaultMaxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
/**
* Set referral cookie with proper security settings
*/
setCookie(
response: any,
referralCode: string,
options: Partial<CookieOptions> = {}
): void {
const cookieOptions: CookieOptions = {
domain: options.domain || undefined, // Root domain for subdomains
path: options.path || '/',
maxAge: options.maxAge || this.defaultMaxAge,
secure: options.secure !== false, // HTTPS only by default
sameSite: options.sameSite || 'lax', // CSRF protection
};
const cookieString = this.buildCookieString(
this.cookieName,
referralCode,
cookieOptions
);
response.setHeader('Set-Cookie', cookieString);
}
/**
* Read referral cookie from request
*/
getCookie(request: any): string | null {
const cookies = this.parseCookies(request.headers.cookie || '');
return cookies[this.cookieName] || null;
}
/**
* Clear referral cookie (after conversion)
*/
clearCookie(response: any, domain?: string): void {
const cookieString = this.buildCookieString(
this.cookieName,
'',
{
domain,
path: '/',
maxAge: 0, // Expire immediately
}
);
response.setHeader('Set-Cookie', cookieString);
}
private buildCookieString(
name: string,
value: string,
options: CookieOptions
): string {
let cookie = `${name}=${encodeURIComponent(value)}`;
if (options.domain) {
cookie += `; Domain=${options.domain}`;
}
if (options.path) {
cookie += `; Path=${options.path}`;
}
if (options.maxAge !== undefined) {
cookie += `; Max-Age=${Math.floor(options.maxAge / 1000)}`;
}
if (options.secure) {
cookie += '; Secure';
}
if (options.sameSite) {
cookie += `; SameSite=${options.sameSite}`;
}
return cookie;
}
private parseCookies(cookieHeader: string): Record<string, string> {
return cookieHeader
.split(';')
.reduce((acc, cookie) => {
const [name, value] = cookie.trim().split('=');
if (name && value) {
acc[name] = decodeURIComponent(value);
}
return acc;
}, {} as Record<string, string>);
}
}
export { ReferralCookieManager };
Reward Systems & Incentive Structures
Effective reward systems balance generosity (to drive sharing) with sustainability (to maintain unit economics). The optimal reward structure depends on your pricing model, customer lifetime value (LTV), and desired viral coefficient.
Automated Reward Engine:
// reward-engine.ts - Multi-tier reward distribution system
interface RewardTier {
name: string;
referralsRequired: number;
rewardType: 'credits' | 'discount' | 'upgrade' | 'cash';
rewardAmount: number;
description: string;
}
interface RewardTransaction {
id: string;
userId: string;
referralId: string;
rewardType: string;
amount: number;
status: 'pending' | 'credited' | 'failed';
createdAt: Date;
processedAt?: Date;
failureReason?: string;
}
class RewardEngine {
private rewardTiers: RewardTier[] = [
{
name: 'Bronze',
referralsRequired: 1,
rewardType: 'credits',
rewardAmount: 500, // 500 API credits
description: 'First referral bonus',
},
{
name: 'Silver',
referralsRequired: 5,
rewardType: 'discount',
rewardAmount: 20, // 20% discount
description: '5 referrals - 20% off next month',
},
{
name: 'Gold',
referralsRequired: 10,
rewardType: 'upgrade',
rewardAmount: 1, // 1 month free upgrade
description: '10 referrals - Free Pro upgrade',
},
{
name: 'Platinum',
referralsRequired: 25,
rewardType: 'cash',
rewardAmount: 100, // $100 cash payout
description: '25 referrals - $100 reward',
},
];
/**
* Calculate reward for successful referral
*/
calculateReward(
referrerId: string,
referredUserId: string,
conversionValue: number
): {
referrerReward: number;
referredReward: number;
type: 'credits' | 'discount' | 'cash';
} {
// Two-sided incentive: reward both referrer and referred user
const referrerReward = Math.floor(conversionValue * 0.20); // 20% commission
const referredReward = Math.floor(conversionValue * 0.10); // 10% signup bonus
return {
referrerReward,
referredReward,
type: 'credits',
};
}
/**
* Process reward transaction with idempotency
*/
async processReward(
userId: string,
referralId: string,
rewardAmount: number,
rewardType: 'credits' | 'discount' | 'cash'
): Promise<RewardTransaction> {
const transaction: RewardTransaction = {
id: uuidv4(),
userId,
referralId,
rewardType,
amount: rewardAmount,
status: 'pending',
createdAt: new Date(),
};
try {
// Check for duplicate transactions (idempotency)
const existing = await this.findTransactionByReferral(referralId);
if (existing) {
console.warn(`Duplicate reward attempt for referral: ${referralId}`);
return existing;
}
// Save transaction record
await this.saveTransaction(transaction);
// Credit reward based on type
switch (rewardType) {
case 'credits':
await this.creditAPICredits(userId, rewardAmount);
break;
case 'discount':
await this.createDiscountCoupon(userId, rewardAmount);
break;
case 'cash':
await this.processCashPayout(userId, rewardAmount);
break;
}
// Update transaction status
transaction.status = 'credited';
transaction.processedAt = new Date();
await this.updateTransaction(transaction);
// Send notification
await this.sendRewardNotification(userId, transaction);
return transaction;
} catch (error) {
transaction.status = 'failed';
transaction.failureReason = error.message;
await this.updateTransaction(transaction);
throw error;
}
}
/**
* Check and unlock tier-based rewards
*/
async checkTierRewards(userId: string): Promise<RewardTier[]> {
const referralCount = await this.getReferralCount(userId);
const unlockedTiers: RewardTier[] = [];
for (const tier of this.rewardTiers) {
if (referralCount >= tier.referralsRequired) {
const alreadyAwarded = await this.isTierAwarded(userId, tier.name);
if (!alreadyAwarded) {
await this.awardTierReward(userId, tier);
unlockedTiers.push(tier);
}
}
}
return unlockedTiers;
}
/**
* Award tier-based milestone reward
*/
private async awardTierReward(
userId: string,
tier: RewardTier
): Promise<void> {
await this.processReward(
userId,
`tier_${tier.name}_${Date.now()}`,
tier.rewardAmount,
tier.rewardType
);
await this.markTierAwarded(userId, tier.name);
}
// Helper methods (implement with your backend)
private async creditAPICredits(userId: string, amount: number): Promise<void> {
// UPDATE users SET api_credits = api_credits + ? WHERE id = ?
}
private async createDiscountCoupon(userId: string, percent: number): Promise<void> {
// INSERT INTO coupons (user_id, discount_percent, expires_at) VALUES (?, ?, ?)
}
private async processCashPayout(userId: string, amount: number): Promise<void> {
// Integrate with Stripe, PayPal, or other payout provider
// CREATE payout in payment processor
}
private async findTransactionByReferral(referralId: string): Promise<RewardTransaction | null> {
return null;
}
private async saveTransaction(tx: RewardTransaction): Promise<void> {}
private async updateTransaction(tx: RewardTransaction): Promise<void> {}
private async getReferralCount(userId: string): Promise<number> { return 0; }
private async isTierAwarded(userId: string, tier: string): Promise<boolean> { return false; }
private async markTierAwarded(userId: string, tier: string): Promise<void> {}
private async sendRewardNotification(userId: string, tx: RewardTransaction): Promise<void> {}
}
export { RewardEngine, RewardTier, RewardTransaction };
Stripe Integration for Cash Payouts:
// payout-automation.ts - Automated cash reward distribution
import Stripe from 'stripe';
interface PayoutRequest {
userId: string;
amount: number; // in cents
currency: 'usd';
description: string;
metadata: Record<string, string>;
}
class PayoutAutomation {
private stripe: Stripe;
private minimumPayout = 2500; // $25 minimum
constructor(secretKey: string) {
this.stripe = new Stripe(secretKey, {
apiVersion: '2023-10-16',
});
}
/**
* Process cash payout to user's connected account
*/
async processPayout(request: PayoutRequest): Promise<Stripe.Payout> {
// Validate minimum payout threshold
if (request.amount < this.minimumPayout) {
throw new Error(
`Minimum payout is $${this.minimumPayout / 100}. Current: $${request.amount / 100}`
);
}
// Get user's Stripe Connect account ID
const stripeAccountId = await this.getUserStripeAccount(request.userId);
if (!stripeAccountId) {
throw new Error('User has not connected Stripe account');
}
// Create payout
const payout = await this.stripe.payouts.create(
{
amount: request.amount,
currency: request.currency,
description: request.description,
metadata: {
user_id: request.userId,
...request.metadata,
},
},
{
stripeAccount: stripeAccountId,
}
);
return payout;
}
/**
* Get pending payout balance for user
*/
async getPendingBalance(userId: string): Promise<number> {
// Query database for unclaimed rewards
const pendingRewards = await this.getPendingRewards(userId);
return pendingRewards.reduce((sum, r) => sum + r.amount, 0);
}
private async getUserStripeAccount(userId: string): Promise<string | null> {
// SELECT stripe_account_id FROM users WHERE id = ?
return null;
}
private async getPendingRewards(userId: string): Promise<RewardTransaction[]> {
return [];
}
}
export { PayoutAutomation, PayoutRequest };
Learn more about implementing referral programs in our complete growth guide.
Fraud Prevention & Abuse Detection
Referral fraud can destroy program economics through fake accounts, bot signups, and incentive abuse. Implement multi-layered fraud detection to protect your program while maintaining user trust.
Fraud Detection System:
// fraud-detector.ts - Multi-layer fraud prevention
interface FraudSignal {
type: 'duplicate_ip' | 'velocity' | 'device_fingerprint' | 'email_pattern' | 'behavior';
severity: 'low' | 'medium' | 'high';
description: string;
score: number; // 0-100, higher = more suspicious
}
interface FraudAnalysis {
userId: string;
referralId: string;
signals: FraudSignal[];
riskScore: number; // 0-100
decision: 'approve' | 'review' | 'reject';
reason?: string;
}
class FraudDetector {
private readonly riskThresholds = {
approve: 30,
review: 70,
};
/**
* Analyze referral conversion for fraud indicators
*/
async analyzeConversion(
referrerId: string,
referredUserId: string,
metadata: {
ip: string;
userAgent: string;
email: string;
createdAt: Date;
}
): Promise<FraudAnalysis> {
const signals: FraudSignal[] = [];
// Check 1: Duplicate IP addresses
const ipSignal = await this.checkDuplicateIP(
referrerId,
referredUserId,
metadata.ip
);
if (ipSignal) signals.push(ipSignal);
// Check 2: Referral velocity (too many too fast)
const velocitySignal = await this.checkReferralVelocity(
referrerId,
metadata.createdAt
);
if (velocitySignal) signals.push(velocitySignal);
// Check 3: Email pattern analysis
const emailSignal = this.checkEmailPattern(metadata.email);
if (emailSignal) signals.push(emailSignal);
// Check 4: Device fingerprint similarity
const deviceSignal = await this.checkDeviceFingerprint(
referrerId,
referredUserId,
metadata.userAgent
);
if (deviceSignal) signals.push(deviceSignal);
// Check 5: Account age and activity
const behaviorSignal = await this.checkAccountBehavior(referredUserId);
if (behaviorSignal) signals.push(behaviorSignal);
// Calculate aggregate risk score
const riskScore = this.calculateRiskScore(signals);
// Make decision
let decision: FraudAnalysis['decision'] = 'approve';
let reason: string | undefined;
if (riskScore >= this.riskThresholds.review) {
decision = 'reject';
reason = `High fraud risk (${riskScore}/100): ${signals.map(s => s.type).join(', ')}`;
} else if (riskScore >= this.riskThresholds.approve) {
decision = 'review';
reason = `Manual review required (${riskScore}/100)`;
}
return {
userId: referredUserId,
referralId: `${referrerId}:${referredUserId}`,
signals,
riskScore,
decision,
reason,
};
}
/**
* Check for duplicate IP addresses (same IP for referrer and referred)
*/
private async checkDuplicateIP(
referrerId: string,
referredUserId: string,
ip: string
): Promise<FraudSignal | null> {
const referrerIPs = await this.getUserIPs(referrerId);
const hashedIP = this.hashIP(ip);
if (referrerIPs.includes(hashedIP)) {
return {
type: 'duplicate_ip',
severity: 'high',
description: 'Referrer and referred user share same IP address',
score: 80,
};
}
return null;
}
/**
* Check referral velocity (suspicious if too many referrals too quickly)
*/
private async checkReferralVelocity(
referrerId: string,
currentTime: Date
): Promise<FraudSignal | null> {
const recentReferrals = await this.getRecentReferrals(
referrerId,
24 * 60 * 60 * 1000 // Last 24 hours
);
// Flag if >10 referrals in 24 hours (adjust based on your thresholds)
if (recentReferrals.length > 10) {
return {
type: 'velocity',
severity: 'medium',
description: `${recentReferrals.length} referrals in 24 hours`,
score: 60,
};
}
return null;
}
/**
* Check email patterns for disposable/temporary email services
*/
private checkEmailPattern(email: string): FraudSignal | null {
const disposableDomains = [
'tempmail.com',
'guerrillamail.com',
'10minutemail.com',
'throwaway.email',
// Add more as needed
];
const domain = email.split('@')[1]?.toLowerCase();
if (disposableDomains.includes(domain)) {
return {
type: 'email_pattern',
severity: 'high',
description: 'Disposable email address detected',
score: 90,
};
}
// Check for sequential numbers (user1@, user2@, etc.)
if (/\d{3,}/.test(email)) {
return {
type: 'email_pattern',
severity: 'low',
description: 'Sequential number pattern in email',
score: 30,
};
}
return null;
}
/**
* Check device fingerprint similarity between referrer and referred
*/
private async checkDeviceFingerprint(
referrerId: string,
referredUserId: string,
userAgent: string
): Promise<FraudSignal | null> {
const referrerAgent = await this.getUserAgent(referrerId);
if (referrerAgent === userAgent) {
return {
type: 'device_fingerprint',
severity: 'medium',
description: 'Identical device fingerprint',
score: 50,
};
}
return null;
}
/**
* Check account behavior patterns
*/
private async checkAccountBehavior(
userId: string
): Promise<FraudSignal | null> {
const user = await this.getUser(userId);
const accountAge = Date.now() - user.createdAt.getTime();
const activityCount = await this.getUserActivityCount(userId);
// Suspicious if account is very new with no activity
if (accountAge < 5 * 60 * 1000 && activityCount === 0) {
return {
type: 'behavior',
severity: 'medium',
description: 'New account with no activity',
score: 40,
};
}
return null;
}
/**
* Calculate aggregate risk score from signals
*/
private calculateRiskScore(signals: FraudSignal[]): number {
if (signals.length === 0) return 0;
// Weighted average based on severity
const weights = { low: 1, medium: 2, high: 3 };
const totalWeight = signals.reduce((sum, s) => sum + weights[s.severity], 0);
const weightedScore = signals.reduce(
(sum, s) => sum + s.score * weights[s.severity],
0
);
return Math.min(100, Math.round(weightedScore / totalWeight));
}
private hashIP(ip: string): string {
const crypto = require('crypto');
return crypto.createHash('sha256').update(ip).digest('hex').substring(0, 12);
}
// Database helper methods
private async getUserIPs(userId: string): Promise<string[]> { return []; }
private async getRecentReferrals(userId: string, timeWindow: number): Promise<any[]> { return []; }
private async getUserAgent(userId: string): Promise<string> { return ''; }
private async getUser(userId: string): Promise<any> { return { createdAt: new Date() }; }
private async getUserActivityCount(userId: string): Promise<number> { return 0; }
}
export { FraudDetector, FraudAnalysis, FraudSignal };
Explore best practices for ChatGPT app monetization including fraud-resistant business models.
Viral Optimization & K-Factor Maximization
The viral coefficient (K-factor) measures how many new users each existing user brings. A K-factor > 1.0 creates exponential growth; < 1.0 requires paid acquisition to maintain growth.
K-Factor Calculator & Optimization:
// k-factor-calculator.ts - Viral growth metrics
interface ViralMetrics {
totalUsers: number;
invitesSent: number;
invitesAccepted: number;
conversions: number;
inviteRate: number; // % of users who invite
acceptanceRate: number; // % of invites accepted
conversionRate: number; // % of accepts who convert
kFactor: number; // Viral coefficient
cycleTime: number; // Days per viral cycle
}
class KFactorCalculator {
/**
* Calculate K-factor and related viral metrics
*/
calculateMetrics(data: {
users: number;
invites: number;
accepts: number;
conversions: number;
cycleTimeDays: number;
}): ViralMetrics {
const inviteRate = data.invites / data.users;
const acceptanceRate = data.accepts / data.invites;
const conversionRate = data.conversions / data.accepts;
// K = (invites per user) × (acceptance rate) × (conversion rate)
const kFactor = inviteRate * acceptanceRate * conversionRate;
return {
totalUsers: data.users,
invitesSent: data.invites,
invitesAccepted: data.accepts,
conversions: data.conversions,
inviteRate,
acceptanceRate,
conversionRate,
kFactor,
cycleTime: data.cycleTimeDays,
};
}
/**
* Project growth based on K-factor
*/
projectGrowth(
currentUsers: number,
kFactor: number,
cycles: number
): number[] {
const growth: number[] = [currentUsers];
for (let i = 1; i <= cycles; i++) {
const newUsers = growth[i - 1] * kFactor;
growth.push(growth[i - 1] + newUsers);
}
return growth;
}
/**
* Calculate required improvements to reach target K-factor
*/
optimizationTargets(
current: ViralMetrics,
targetKFactor: number
): {
currentK: number;
targetK: number;
gap: number;
recommendations: string[];
} {
const gap = targetKFactor - current.kFactor;
const recommendations: string[] = [];
// Analyze which metric has most improvement potential
if (current.inviteRate < 0.3) {
const requiredInviteRate = targetKFactor / (current.acceptanceRate * current.conversionRate);
recommendations.push(
`Increase invite rate from ${(current.inviteRate * 100).toFixed(1)}% to ${(requiredInviteRate * 100).toFixed(1)}%`
);
}
if (current.acceptanceRate < 0.2) {
const requiredAcceptRate = targetKFactor / (current.inviteRate * current.conversionRate);
recommendations.push(
`Increase acceptance rate from ${(current.acceptanceRate * 100).toFixed(1)}% to ${(requiredAcceptRate * 100).toFixed(1)}%`
);
}
if (current.conversionRate < 0.5) {
const requiredConvRate = targetKFactor / (current.inviteRate * current.acceptanceRate);
recommendations.push(
`Increase conversion rate from ${(current.conversionRate * 100).toFixed(1)}% to ${(requiredConvRate * 100).toFixed(1)}%`
);
}
return {
currentK: current.kFactor,
targetK: targetKFactor,
gap,
recommendations,
};
}
}
export { KFactorCalculator, ViralMetrics };
Social Sharing Widget:
// sharing-widget.tsx - Viral sharing component
import React, { useState } from 'react';
interface SharingWidgetProps {
referralCode: string;
referralUrl: string;
userName: string;
incentiveText: string;
}
const SharingWidget: React.FC<SharingWidgetProps> = ({
referralCode,
referralUrl,
userName,
incentiveText,
}) => {
const [copied, setCopied] = useState(false);
const shareMessage = `Hey! I'm using MakeAIHQ to build ChatGPT apps without coding. ${incentiveText} Sign up with my link: ${referralUrl}`;
const copyToClipboard = () => {
navigator.clipboard.writeText(referralUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const shareVia = {
twitter: `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareMessage)}`,
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(referralUrl)}`,
facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(referralUrl)}`,
email: `mailto:?subject=${encodeURIComponent('Try MakeAIHQ - Build ChatGPT Apps')}&body=${encodeURIComponent(shareMessage)}`,
};
return (
<div className="sharing-widget">
<div className="referral-stats">
<h3>Share & Earn</h3>
<p className="incentive">{incentiveText}</p>
<div className="referral-link">
<input
type="text"
value={referralUrl}
readOnly
onClick={(e) => e.currentTarget.select()}
/>
<button onClick={copyToClipboard} className={copied ? 'copied' : ''}>
{copied ? 'Copied!' : 'Copy Link'}
</button>
</div>
<div className="referral-code">
<span>Your code:</span>
<strong>{referralCode}</strong>
</div>
</div>
<div className="social-share">
<h4>Share on social media:</h4>
<div className="share-buttons">
<a
href={shareVia.twitter}
target="_blank"
rel="noopener noreferrer"
className="share-btn twitter"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
</svg>
Twitter
</a>
<a
href={shareVia.linkedin}
target="_blank"
rel="noopener noreferrer"
className="share-btn linkedin"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
LinkedIn
</a>
<a
href={shareVia.facebook}
target="_blank"
rel="noopener noreferrer"
className="share-btn facebook"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
Facebook
</a>
<a
href={shareVia.email}
className="share-btn email"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>
</svg>
Email
</a>
</div>
</div>
<style jsx>{`
.sharing-widget {
background: #f8f9fa;
border-radius: 8px;
padding: 24px;
max-width: 500px;
}
.referral-stats h3 {
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 600;
}
.incentive {
color: #28a745;
font-weight: 500;
margin-bottom: 16px;
}
.referral-link {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.referral-link input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.referral-link button {
padding: 10px 20px;
background: #0066cc;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background 0.2s;
}
.referral-link button.copied {
background: #28a745;
}
.referral-code {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: white;
border-radius: 4px;
margin-bottom: 20px;
}
.referral-code strong {
font-size: 18px;
font-family: monospace;
color: #0066cc;
}
.social-share h4 {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
color: #666;
}
.share-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.share-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border-radius: 4px;
text-decoration: none;
font-weight: 500;
font-size: 14px;
transition: opacity 0.2s;
}
.share-btn:hover {
opacity: 0.9;
}
.share-btn.twitter {
background: #1DA1F2;
color: white;
}
.share-btn.linkedin {
background: #0077B5;
color: white;
}
.share-btn.facebook {
background: #1877F2;
color: white;
}
.share-btn.email {
background: #666;
color: white;
}
`}</style>
</div>
);
};
export default SharingWidget;
Discover ChatGPT app deployment strategies to maximize viral distribution.
Analytics & Performance Tracking
Comprehensive analytics enable data-driven optimization of your referral funnel, from initial share to conversion and beyond.
Referral Analytics Dashboard:
// referral-dashboard.tsx - Real-time referral analytics
import React, { useEffect, useState } from 'react';
interface ReferralStats {
totalReferrals: number;
activeReferrals: number;
conversionRate: number;
totalRewardsEarned: number;
pendingRewards: number;
kFactor: number;
topReferrers: Array<{
userId: string;
userName: string;
referrals: number;
rewards: number;
}>;
timeline: Array<{
date: string;
referrals: number;
conversions: number;
}>;
}
const ReferralDashboard: React.FC<{ userId: string }> = ({ userId }) => {
const [stats, setStats] = useState<ReferralStats | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchReferralStats();
}, [userId]);
const fetchReferralStats = async () => {
try {
const response = await fetch(`/api/referrals/stats?userId=${userId}`);
const data = await response.json();
setStats(data);
} catch (error) {
console.error('Failed to fetch referral stats:', error);
} finally {
setLoading(false);
}
};
if (loading) return <div>Loading...</div>;
if (!stats) return <div>No data available</div>;
return (
<div className="referral-dashboard">
<h2>Referral Performance</h2>
<div className="stats-grid">
<div className="stat-card">
<div className="stat-value">{stats.totalReferrals}</div>
<div className="stat-label">Total Referrals</div>
</div>
<div className="stat-card">
<div className="stat-value">{stats.activeReferrals}</div>
<div className="stat-label">Active Users</div>
</div>
<div className="stat-card">
<div className="stat-value">
{(stats.conversionRate * 100).toFixed(1)}%
</div>
<div className="stat-label">Conversion Rate</div>
</div>
<div className="stat-card">
<div className="stat-value">${stats.totalRewardsEarned}</div>
<div className="stat-label">Rewards Earned</div>
</div>
<div className="stat-card">
<div className="stat-value">{stats.kFactor.toFixed(2)}</div>
<div className="stat-label">K-Factor</div>
<div className="stat-hint">
{stats.kFactor >= 1 ? '🚀 Viral growth!' : '📈 Growing'}
</div>
</div>
</div>
<div className="top-referrers">
<h3>Top Referrers</h3>
<table>
<thead>
<tr>
<th>User</th>
<th>Referrals</th>
<th>Rewards</th>
</tr>
</thead>
<tbody>
{stats.topReferrers.map((referrer) => (
<tr key={referrer.userId}>
<td>{referrer.userName}</td>
<td>{referrer.referrals}</td>
<td>${referrer.rewards}</td>
</tr>
))}
</tbody>
</table>
</div>
<style jsx>{`
.referral-dashboard {
padding: 24px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-value {
font-size: 32px;
font-weight: 700;
color: #0066cc;
margin-bottom: 4px;
}
.stat-label {
font-size: 14px;
color: #666;
}
.stat-hint {
font-size: 12px;
margin-top: 4px;
}
.top-referrers {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.top-referrers h3 {
margin: 0 0 16px 0;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
text-align: left;
padding: 12px;
border-bottom: 2px solid #ddd;
font-weight: 600;
}
td {
padding: 12px;
border-bottom: 1px solid #eee;
}
`}</style>
</div>
);
};
export default ReferralDashboard;
Database Schema for Referral System:
-- database-schema.sql - Complete referral system tables
-- Referral codes table
CREATE TABLE referral_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(12) UNIQUE NOT NULL,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
campaign_id UUID REFERENCES campaigns(id),
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP,
metadata JSONB DEFAULT '{}',
INDEX idx_code (code),
INDEX idx_user_id (user_id)
);
-- Referral clicks table (track attribution)
CREATE TABLE referral_clicks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_code VARCHAR(12) NOT NULL,
clicked_at TIMESTAMP DEFAULT NOW(),
ip_hash VARCHAR(64) NOT NULL,
user_agent TEXT,
referrer TEXT,
utm_source VARCHAR(100),
utm_medium VARCHAR(100),
utm_campaign VARCHAR(100),
fingerprint VARCHAR(32),
INDEX idx_referral_code (referral_code),
INDEX idx_fingerprint (fingerprint),
INDEX idx_clicked_at (clicked_at)
);
-- Referral conversions table
CREATE TABLE referral_conversions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_code VARCHAR(12) NOT NULL,
referrer_id UUID NOT NULL REFERENCES users(id),
referred_user_id UUID NOT NULL REFERENCES users(id),
converted_at TIMESTAMP DEFAULT NOW(),
attribution_method VARCHAR(20) NOT NULL,
reward_status VARCHAR(20) DEFAULT 'pending',
fraud_score INTEGER DEFAULT 0,
fraud_signals JSONB DEFAULT '[]',
UNIQUE(referrer_id, referred_user_id),
INDEX idx_referrer_id (referrer_id),
INDEX idx_referred_user_id (referred_user_id),
INDEX idx_reward_status (reward_status)
);
-- Reward transactions table
CREATE TABLE reward_transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
referral_id UUID REFERENCES referral_conversions(id),
reward_type VARCHAR(20) NOT NULL,
amount INTEGER NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT NOW(),
processed_at TIMESTAMP,
failure_reason TEXT,
metadata JSONB DEFAULT '{}',
INDEX idx_user_id (user_id),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
);
-- Tier achievements table
CREATE TABLE tier_achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
tier_name VARCHAR(50) NOT NULL,
achieved_at TIMESTAMP DEFAULT NOW(),
reward_amount INTEGER,
UNIQUE(user_id, tier_name),
INDEX idx_user_id (user_id)
);
-- Materialized view for referral stats
CREATE MATERIALIZED VIEW referral_stats AS
SELECT
rc.referrer_id,
COUNT(*) as total_referrals,
COUNT(CASE WHEN u.status = 'active' THEN 1 END) as active_referrals,
SUM(rt.amount) as total_rewards,
AVG(EXTRACT(EPOCH FROM (rc.converted_at - u.created_at)) / 86400) as avg_conversion_time_days
FROM referral_conversions rc
JOIN users u ON u.id = rc.referred_user_id
LEFT JOIN reward_transactions rt ON rt.referral_id = rc.id AND rt.status = 'credited'
GROUP BY rc.referrer_id;
CREATE UNIQUE INDEX idx_referral_stats_user ON referral_stats(referrer_id);
-- Refresh materialized view (run periodically via cron)
-- REFRESH MATERIALIZED VIEW CONCURRENTLY referral_stats;
Learn about ChatGPT app analytics implementation for comprehensive tracking strategies.
Conclusion
Referral programs deliver 5-10x lower customer acquisition costs and 37% higher retention rates compared to paid advertising channels. By implementing robust tracking architecture, automated reward systems, multi-layer fraud prevention, and viral optimization strategies, you can build a referral engine that compounds growth over time.
The code examples in this guide provide production-ready implementations for referral tracking with multi-device attribution, automated reward distribution with Stripe integration, fraud detection with behavioral analysis, K-factor optimization, and comprehensive analytics dashboards. Start with basic referral tracking and cookie attribution, then layer in fraud prevention and tier-based rewards as your program scales.
Ready to build viral growth into your ChatGPT app? Start building with MakeAIHQ - the no-code platform for ChatGPT app development. Deploy referral programs, analytics dashboards, and monetization systems without writing code. Get your first ChatGPT app live in 48 hours with our AI-powered builder.
Related Resources:
- ChatGPT App Growth Strategies: Complete Marketing Guide
- ChatGPT App Monetization Strategies
- ChatGPT App Analytics Implementation
- Viral Growth Strategies for SaaS
- Referral Marketing Best Practices
- Building Viral Products
External Resources: