Referral Program Design for ChatGPT Apps: Complete Implementation Guide
Referral programs are the most cost-effective customer acquisition channel for ChatGPT apps, delivering 30-40% lower customer acquisition costs (CAC) compared to paid advertising while generating higher-quality users with 2x better retention rates. When designed correctly, a referral program creates a self-sustaining viral loop where each new customer brings 1.2+ additional customers, achieving a k-factor above 1.0 for exponential growth.
The key to successful referral programs lies in three critical design decisions: incentive structure (one-sided vs two-sided rewards), friction reduction (how easy it is to share), and fraud prevention (blocking gaming and abuse). ChatGPT apps have unique advantages for referral programs because users naturally want to share AI-powered solutions that solve real problems—your app becomes a conversation starter rather than a marketing pitch.
This guide provides production-ready implementations for complete referral systems, including unique code generation, reward distribution, viral loop optimization, fraud detection, and analytics tracking. Whether you're building your first referral program or optimizing an existing one, these battle-tested code examples will help you create a referral engine that drives predictable, sustainable growth.
By the end of this article, you'll have a fully functional referral system that generates trackable referral codes, distributes rewards automatically, prevents fraud, and measures viral coefficient with precision.
Understanding Referral Mechanics
Effective referral programs balance three components: incentive design (what rewards motivate sharing), user experience (how easy it is to refer), and trust signals (why people believe the program is legitimate).
One-Sided vs Two-Sided Incentives
One-sided programs reward only the referrer (e.g., "Get $50 credit for each friend who signs up"). These programs work well when your product has strong inherent value—the referred user doesn't need incentive because they genuinely benefit from using your ChatGPT app. One-sided programs are simpler to implement and reduce fraud risk because there's no incentive for users to create fake accounts.
Two-sided programs reward both referrer and referee (e.g., "You get $25, your friend gets $25"). These programs reduce friction by giving the referee immediate value, increasing conversion rates by 40-60%. However, they attract more fraud attempts because both parties have financial incentive to game the system. Two-sided programs work best for ChatGPT apps with high switching costs (e.g., migrating from competitor tools) where the referee needs extra motivation to try your product.
Reward Structures That Drive Action
Credits vs discounts vs cash: Account credits (e.g., "$50 toward your next subscription") create locked-in value that keeps users engaged, but cash rewards (e.g., "Earn $25 via PayPal") generate higher sharing rates because recipients can use rewards anywhere. For ChatGPT apps, credits work well for Professional/Business tier users (they'll use the credit), while cash rewards convert Free tier users faster.
Graduated rewards: Offer increasing rewards for multiple referrals (e.g., 1st referral = $10, 5th referral = $25, 10th referral = $100). This creates gamification and encourages top advocates to become "super referrers" who drive 80% of total referrals.
Fraud Prevention Fundamentals
Referral fraud costs SaaS companies 15-25% of referral budgets through fake accounts, self-referrals, and automated abuse. Effective fraud detection combines real-time validation (blocking obvious fraud instantly) with post-conversion analysis (catching sophisticated patterns over time).
Key fraud signals include: IP address duplication (multiple accounts from same IP), device fingerprinting (same browser/device creating accounts), email patterns (disposable email domains, sequential addresses), timing anomalies (accounts created within seconds), and behavioral signals (zero product usage after signup).
Referral System Implementation
This production-ready referral system generates unique codes, tracks referral attribution, and handles reward distribution automatically.
Referral Code Generator
// lib/referral/code-generator.ts
import { createHash, randomBytes } from 'crypto';
import { db } from '../firebase/admin';
interface ReferralCodeOptions {
userId: string;
codeLength?: number;
prefix?: string;
expirationDays?: number;
}
interface ReferralCode {
code: string;
userId: string;
createdAt: Date;
expiresAt: Date | null;
usageCount: number;
maxUsage: number | null;
metadata: Record<string, any>;
}
export class ReferralCodeGenerator {
private static readonly DEFAULT_CODE_LENGTH = 8;
private static readonly DEFAULT_PREFIX = 'REF';
private static readonly COLLISION_RETRY_LIMIT = 5;
/**
* Generate unique referral code with collision detection
*/
static async generateCode(options: ReferralCodeOptions): Promise<ReferralCode> {
const {
userId,
codeLength = this.DEFAULT_CODE_LENGTH,
prefix = this.DEFAULT_PREFIX,
expirationDays = null
} = options;
let attempts = 0;
let code: string;
let exists = true;
// Retry loop to handle collisions
while (exists && attempts < this.COLLISION_RETRY_LIMIT) {
code = this.createCode(userId, codeLength, prefix);
exists = await this.codeExists(code);
attempts++;
}
if (exists) {
throw new Error('Failed to generate unique referral code after maximum retries');
}
const referralCode: ReferralCode = {
code: code!,
userId,
createdAt: new Date(),
expiresAt: expirationDays ? new Date(Date.now() + expirationDays * 86400000) : null,
usageCount: 0,
maxUsage: null,
metadata: {}
};
// Store in Firestore
await db.collection('referralCodes').doc(code!).set(referralCode);
return referralCode;
}
/**
* Create code string with entropy and readability
*/
private static createCode(userId: string, length: number, prefix: string): string {
// Mix deterministic hash with random bytes for uniqueness
const hash = createHash('sha256').update(userId + Date.now()).digest('hex');
const random = randomBytes(16).toString('hex');
const combined = hash + random;
// Use base32 alphabet (excludes confusing characters: 0, O, I, 1)
const base32 = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
let code = '';
for (let i = 0; i < length; i++) {
const index = parseInt(combined.substr(i * 2, 2), 16) % base32.length;
code += base32[index];
}
return prefix ? `${prefix}-${code}` : code;
}
/**
* Check if code already exists
*/
private static async codeExists(code: string): Promise<boolean> {
const doc = await db.collection('referralCodes').doc(code).get();
return doc.exists;
}
/**
* Validate and retrieve referral code
*/
static async validateCode(code: string): Promise<ReferralCode | null> {
const doc = await db.collection('referralCodes').doc(code).get();
if (!doc.exists) {
return null;
}
const referralCode = doc.data() as ReferralCode;
// Check expiration
if (referralCode.expiresAt && new Date(referralCode.expiresAt) < new Date()) {
return null;
}
// Check max usage
if (referralCode.maxUsage && referralCode.usageCount >= referralCode.maxUsage) {
return null;
}
return referralCode;
}
}
Referral Tracker
// lib/referral/tracker.ts
import { db } from '../firebase/admin';
import { FieldValue } from 'firebase-admin/firestore';
interface ReferralConversion {
referrerId: string;
refereeId: string;
referralCode: string;
conversionType: 'signup' | 'trial' | 'paid' | 'custom';
conversionValue: number;
metadata: Record<string, any>;
timestamp: Date;
}
interface ReferralAttribution {
refereeId: string;
referralCode: string;
referrerId: string;
firstTouchAt: Date;
lastTouchAt: Date;
touchCount: number;
converted: boolean;
conversionAt: Date | null;
}
export class ReferralTracker {
/**
* Track referral attribution on user signup
*/
static async trackReferral(
refereeId: string,
referralCode: string
): Promise<ReferralAttribution> {
// Validate referral code
const codeDoc = await db.collection('referralCodes').doc(referralCode).get();
if (!codeDoc.exists) {
throw new Error('Invalid referral code');
}
const { userId: referrerId } = codeDoc.data()!;
// Prevent self-referrals
if (referrerId === refereeId) {
throw new Error('Self-referrals not allowed');
}
const attribution: ReferralAttribution = {
refereeId,
referralCode,
referrerId,
firstTouchAt: new Date(),
lastTouchAt: new Date(),
touchCount: 1,
converted: false,
conversionAt: null
};
// Store attribution
await db.collection('referralAttributions').doc(refereeId).set(attribution);
// Increment code usage count
await db.collection('referralCodes').doc(referralCode).update({
usageCount: FieldValue.increment(1)
});
return attribution;
}
/**
* Record conversion event (trial start, payment, etc.)
*/
static async recordConversion(
refereeId: string,
conversionType: ReferralConversion['conversionType'],
conversionValue: number = 0,
metadata: Record<string, any> = {}
): Promise<ReferralConversion | null> {
// Get attribution
const attributionDoc = await db.collection('referralAttributions').doc(refereeId).get();
if (!attributionDoc.exists) {
return null; // No referral attribution
}
const attribution = attributionDoc.data() as ReferralAttribution;
// Don't re-convert
if (attribution.converted) {
return null;
}
const conversion: ReferralConversion = {
referrerId: attribution.referrerId,
refereeId,
referralCode: attribution.referralCode,
conversionType,
conversionValue,
metadata,
timestamp: new Date()
};
// Store conversion
await db.collection('referralConversions').add(conversion);
// Update attribution
await db.collection('referralAttributions').doc(refereeId).update({
converted: true,
conversionAt: new Date()
});
// Update referrer stats
await db.collection('users').doc(attribution.referrerId).update({
'referralStats.totalConversions': FieldValue.increment(1),
'referralStats.totalValue': FieldValue.increment(conversionValue),
'referralStats.lastConversionAt': new Date()
});
return conversion;
}
/**
* Get referral stats for user
*/
static async getReferralStats(userId: string) {
const conversionsSnapshot = await db
.collection('referralConversions')
.where('referrerId', '==', userId)
.get();
const conversions = conversionsSnapshot.docs.map(doc => doc.data());
return {
totalReferrals: conversions.length,
totalValue: conversions.reduce((sum, c) => sum + c.conversionValue, 0),
byType: conversions.reduce((acc, c) => {
acc[c.conversionType] = (acc[c.conversionType] || 0) + 1;
return acc;
}, {} as Record<string, number>)
};
}
}
Reward Distributor
// lib/referral/reward-distributor.ts
import { db } from '../firebase/admin';
import { FieldValue } from 'firebase-admin/firestore';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16'
});
interface RewardRule {
conversionType: string;
referrerReward: number;
refereeReward: number;
rewardType: 'credit' | 'cash' | 'discount';
maxRewardsPerUser?: number;
}
interface RewardDistribution {
userId: string;
amount: number;
type: 'credit' | 'cash' | 'discount';
referralCode: string;
conversionId: string;
distributedAt: Date;
status: 'pending' | 'completed' | 'failed';
}
export class RewardDistributor {
private static readonly DEFAULT_RULES: RewardRule[] = [
{
conversionType: 'signup',
referrerReward: 0,
refereeReward: 0,
rewardType: 'credit'
},
{
conversionType: 'trial',
referrerReward: 10,
refereeReward: 10,
rewardType: 'credit'
},
{
conversionType: 'paid',
referrerReward: 50,
refereeReward: 25,
rewardType: 'credit',
maxRewardsPerUser: 100 // Max 100 referrals
}
];
/**
* Distribute rewards based on conversion
*/
static async distributeRewards(
conversion: ReferralConversion
): Promise<RewardDistribution[]> {
const rule = this.DEFAULT_RULES.find(r => r.conversionType === conversion.conversionType);
if (!rule) {
throw new Error(`No reward rule for conversion type: ${conversion.conversionType}`);
}
const distributions: RewardDistribution[] = [];
// Check referrer reward limit
const referrerStats = await this.getUserRewardStats(conversion.referrerId);
const canRewardReferrer = !rule.maxRewardsPerUser ||
referrerStats.totalRewards < rule.maxRewardsPerUser;
// Distribute referrer reward
if (canRewardReferrer && rule.referrerReward > 0) {
const referrerDistribution = await this.distributeReward(
conversion.referrerId,
rule.referrerReward,
rule.rewardType,
conversion.referralCode,
conversion
);
distributions.push(referrerDistribution);
}
// Distribute referee reward
if (rule.refereeReward > 0) {
const refereeDistribution = await this.distributeReward(
conversion.refereeId,
rule.refereeReward,
rule.rewardType,
conversion.referralCode,
conversion
);
distributions.push(refereeDistribution);
}
return distributions;
}
/**
* Distribute single reward
*/
private static async distributeReward(
userId: string,
amount: number,
type: RewardDistribution['type'],
referralCode: string,
conversion: ReferralConversion
): Promise<RewardDistribution> {
const distribution: RewardDistribution = {
userId,
amount,
type,
referralCode,
conversionId: conversion.refereeId, // Use refereeId as unique ID
distributedAt: new Date(),
status: 'pending'
};
try {
if (type === 'credit') {
await this.distributeCreditReward(userId, amount);
} else if (type === 'cash') {
await this.distributeCashReward(userId, amount);
} else if (type === 'discount') {
await this.distributeDiscountReward(userId, amount);
}
distribution.status = 'completed';
} catch (error) {
distribution.status = 'failed';
console.error('Reward distribution failed:', error);
}
// Store distribution record
await db.collection('rewardDistributions').add(distribution);
return distribution;
}
/**
* Add account credit
*/
private static async distributeCreditReward(userId: string, amount: number): Promise<void> {
await db.collection('users').doc(userId).update({
'billing.accountCredit': FieldValue.increment(amount),
'referralStats.totalCreditsEarned': FieldValue.increment(amount)
});
}
/**
* Send cash via Stripe
*/
private static async distributeCashReward(userId: string, amount: number): Promise<void> {
const userDoc = await db.collection('users').doc(userId).get();
const { stripeCustomerId, email } = userDoc.data()!;
// Create payout or refund to customer
await stripe.payouts.create({
amount: amount * 100, // Convert to cents
currency: 'usd',
destination: stripeCustomerId,
description: `Referral reward: $${amount}`
});
}
/**
* Generate discount coupon
*/
private static async distributeDiscountReward(userId: string, amount: number): Promise<void> {
const coupon = await stripe.coupons.create({
amount_off: amount * 100,
currency: 'usd',
duration: 'once',
name: `Referral Discount: $${amount}`
});
await db.collection('users').doc(userId).update({
'billing.discountCoupons': FieldValue.arrayUnion(coupon.id)
});
}
/**
* Get user reward statistics
*/
private static async getUserRewardStats(userId: string) {
const distributionsSnapshot = await db
.collection('rewardDistributions')
.where('userId', '==', userId)
.where('status', '==', 'completed')
.get();
return {
totalRewards: distributionsSnapshot.size,
totalAmount: distributionsSnapshot.docs.reduce(
(sum, doc) => sum + doc.data().amount,
0
)
};
}
}
Viral Loop Optimization
Create frictionless sharing experiences that encourage users to spread your ChatGPT app organically.
Share Component
// components/ReferralShareWidget.tsx
import React, { useState, useEffect } from 'react';
import { ReferralCodeGenerator } from '../lib/referral/code-generator';
interface ShareChannel {
name: string;
icon: string;
shareUrl: (code: string, message: string) => string;
trackingEvent: string;
}
export function ReferralShareWidget({ userId }: { userId: string }) {
const [referralCode, setReferralCode] = useState<string>('');
const [copied, setCopied] = useState(false);
const [shareCount, setShareCount] = useState(0);
useEffect(() => {
loadReferralCode();
}, [userId]);
async function loadReferralCode() {
// Generate or retrieve existing code
const code = await ReferralCodeGenerator.generateCode({ userId });
setReferralCode(code.code);
}
const shareChannels: ShareChannel[] = [
{
name: 'Email',
icon: '📧',
shareUrl: (code, msg) =>
`mailto:?subject=${encodeURIComponent('Try this ChatGPT app builder')}&body=${encodeURIComponent(msg + ' ' + getReferralLink(code))}`,
trackingEvent: 'referral_share_email'
},
{
name: 'Twitter',
icon: '𝕏',
shareUrl: (code, msg) =>
`https://twitter.com/intent/tweet?text=${encodeURIComponent(msg)}&url=${encodeURIComponent(getReferralLink(code))}`,
trackingEvent: 'referral_share_twitter'
},
{
name: 'LinkedIn',
icon: '💼',
shareUrl: (code, msg) =>
`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(getReferralLink(code))}`,
trackingEvent: 'referral_share_linkedin'
},
{
name: 'Facebook',
icon: '📘',
shareUrl: (code, msg) =>
`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(getReferralLink(code))}`,
trackingEvent: 'referral_share_facebook'
}
];
function getReferralLink(code: string): string {
return `https://makeaihq.com/signup?ref=${code}`;
}
function getShareMessage(): string {
return "I'm using MakeAIHQ to build ChatGPT apps without coding. You should try it—we both get $25 credit!";
}
async function handleShare(channel: ShareChannel) {
const shareUrl = channel.shareUrl(referralCode, getShareMessage());
window.open(shareUrl, '_blank', 'width=600,height=400');
// Track share event
trackShareEvent(channel.trackingEvent);
setShareCount(prev => prev + 1);
}
async function copyLink() {
await navigator.clipboard.writeText(getReferralLink(referralCode));
setCopied(true);
setTimeout(() => setCopied(false), 2000);
trackShareEvent('referral_copy_link');
}
function trackShareEvent(eventName: string) {
// Send to analytics
if (window.gtag) {
window.gtag('event', eventName, {
referral_code: referralCode,
user_id: userId
});
}
}
return (
<div className="referral-share-widget">
<div className="referral-code-display">
<label>Your Referral Code</label>
<div className="code-box">
<code>{referralCode}</code>
<button onClick={copyLink}>
{copied ? '✓ Copied!' : 'Copy Link'}
</button>
</div>
</div>
<div className="share-channels">
<h3>Share with friends</h3>
<div className="channel-grid">
{shareChannels.map(channel => (
<button
key={channel.name}
onClick={() => handleShare(channel)}
className="share-button"
>
<span className="icon">{channel.icon}</span>
<span className="label">{channel.name}</span>
</button>
))}
</div>
</div>
<div className="referral-stats">
<p>Total shares: <strong>{shareCount}</strong></p>
<p>Earn $50 for each friend who upgrades to Professional tier</p>
</div>
</div>
);
}
Email Invite Sender
// lib/referral/email-inviter.ts
import { EmailClient } from '@azure/communication-email';
interface EmailInvite {
senderUserId: string;
recipientEmail: string;
recipientName: string;
referralCode: string;
personalMessage?: string;
}
export class EmailInviter {
private emailClient: EmailClient;
constructor() {
this.emailClient = new EmailClient(
process.env.AZURE_COMMUNICATION_CONNECTION_STRING!
);
}
/**
* Send personalized referral invite email
*/
async sendInvite(invite: EmailInvite): Promise<void> {
const { senderUserId, recipientEmail, recipientName, referralCode, personalMessage } = invite;
// Get sender info
const senderDoc = await db.collection('users').doc(senderUserId).get();
const senderName = senderDoc.data()?.displayName || 'A friend';
const referralLink = `https://makeaihq.com/signup?ref=${referralCode}`;
const emailHtml = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #0A0E27 0%, #1a1f3a 100%); color: white; padding: 30px; text-align: center; }
.content { background: white; padding: 30px; border: 1px solid #e0e0e0; }
.cta-button { display: inline-block; background: #D4AF37; color: #0A0E27; padding: 15px 30px; text-decoration: none; border-radius: 5px; font-weight: bold; margin: 20px 0; }
.personal-message { background: #f9f9f9; border-left: 4px solid #D4AF37; padding: 15px; margin: 20px 0; font-style: italic; }
.benefits { list-style: none; padding: 0; }
.benefits li { padding: 10px 0; padding-left: 30px; background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path fill="%23D4AF37" d="M0 11l2-2 5 5L18 3l2 2L7 18z"/></svg>') no-repeat left center; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 Build ChatGPT Apps Without Code</h1>
</div>
<div class="content">
<p>Hi ${recipientName},</p>
<p><strong>${senderName}</strong> invited you to try MakeAIHQ, the no-code platform for building ChatGPT apps.</p>
${personalMessage ? `
<div class="personal-message">
"${personalMessage}"
<br>— ${senderName}
</div>
` : ''}
<h2>What you get:</h2>
<ul class="benefits">
<li><strong>$25 account credit</strong> when you sign up</li>
<li><strong>48-hour app creation</strong> from idea to ChatGPT App Store</li>
<li><strong>No coding required</strong> — AI builds your app from prompts</li>
<li><strong>800M potential users</strong> on ChatGPT platform</li>
</ul>
<center>
<a href="${referralLink}" class="cta-button">
Claim Your $25 Credit →
</a>
</center>
<p style="margin-top: 30px; font-size: 14px; color: #666;">
This referral link gives you $25 in account credit. ${senderName} will also receive a reward when you upgrade to a paid plan.
</p>
</div>
</div>
</body>
</html>
`;
const message = {
senderAddress: 'referrals@makeaihq.com',
content: {
subject: `${senderName} invited you to build ChatGPT apps (+ $25 credit)`,
html: emailHtml
},
recipients: {
to: [{ address: recipientEmail, displayName: recipientName }]
}
};
await this.emailClient.beginSend(message);
// Track invite sent
await db.collection('referralInvites').add({
senderUserId,
recipientEmail,
referralCode,
sentAt: new Date(),
status: 'sent'
});
}
/**
* Send bulk invites (with rate limiting)
*/
async sendBulkInvites(
senderUserId: string,
recipients: Array<{ email: string; name: string }>,
referralCode: string
): Promise<void> {
const BATCH_SIZE = 10;
const DELAY_MS = 1000;
for (let i = 0; i < recipients.length; i += BATCH_SIZE) {
const batch = recipients.slice(i, i + BATCH_SIZE);
await Promise.all(
batch.map(recipient =>
this.sendInvite({
senderUserId,
recipientEmail: recipient.email,
recipientName: recipient.name,
referralCode
})
)
);
// Rate limiting
if (i + BATCH_SIZE < recipients.length) {
await new Promise(resolve => setTimeout(resolve, DELAY_MS));
}
}
}
}
Social Share API
// lib/referral/social-share.ts
import { db } from '../firebase/admin';
interface SocialShareConfig {
platform: 'twitter' | 'linkedin' | 'facebook' | 'reddit';
userId: string;
referralCode: string;
customMessage?: string;
}
export class SocialShareAPI {
/**
* Generate optimized share content for each platform
*/
static async generateShareContent(config: SocialShareConfig): Promise<string> {
const { platform, referralCode, customMessage } = config;
const referralLink = `https://makeaihq.com/signup?ref=${referralCode}`;
const templates = {
twitter: customMessage ||
`🚀 I'm building ChatGPT apps without code using @MakeAIHQ\n\n` +
`✅ No coding required\n` +
`✅ 48hrs to ChatGPT App Store\n` +
`✅ 800M potential users\n\n` +
`Try it (we both get $25):\n${referralLink}`,
linkedin: customMessage ||
`I've been using MakeAIHQ to build ChatGPT applications without writing code. ` +
`The platform makes it incredibly easy to deploy apps to the ChatGPT App Store ` +
`and reach 800 million users.\n\n` +
`If you're interested in building AI-powered apps, check it out: ${referralLink}\n\n` +
`#ChatGPT #NoCode #AI #SaaS`,
facebook: customMessage ||
`I just discovered an amazing no-code platform for building ChatGPT apps! ` +
`MakeAIHQ lets you create and publish apps to the ChatGPT App Store in 48 hours—no coding required.\n\n` +
`Sign up and we both get $25 credit: ${referralLink}`,
reddit: customMessage ||
`**PSA: You can now build ChatGPT apps without coding**\n\n` +
`I've been using MakeAIHQ for the past month and it's genuinely game-changing. ` +
`You describe your app in plain English, and it generates the full MCP server + widgets.\n\n` +
`Key benefits:\n` +
`- No coding required (seriously)\n` +
`- Deploy to ChatGPT App Store in ~48 hours\n` +
`- Reach 800M ChatGPT users\n` +
`- Free tier to get started\n\n` +
`Full disclosure: This is a referral link (we both get $25 credit if you sign up): ${referralLink}`
};
// Track share generation
await this.trackShareEvent(config);
return templates[platform];
}
/**
* Track social share events
*/
private static async trackShareEvent(config: SocialShareConfig): Promise<void> {
await db.collection('referralShares').add({
userId: config.userId,
platform: config.platform,
referralCode: config.referralCode,
sharedAt: new Date()
});
// Increment user stats
await db.collection('users').doc(config.userId).update({
[`referralStats.sharesByPlatform.${config.platform}`]:
admin.firestore.FieldValue.increment(1)
});
}
/**
* Get share performance analytics
*/
static async getShareAnalytics(userId: string) {
const sharesSnapshot = await db
.collection('referralShares')
.where('userId', '==', userId)
.get();
const shares = sharesSnapshot.docs.map(doc => doc.data());
return {
totalShares: shares.length,
byPlatform: shares.reduce((acc, share) => {
acc[share.platform] = (acc[share.platform] || 0) + 1;
return acc;
}, {} as Record<string, number>),
lastSharedAt: shares.length > 0
? shares.sort((a, b) => b.sharedAt - a.sharedAt)[0].sharedAt
: null
};
}
}
Fraud Detection
Protect your referral program from abuse while maintaining a frictionless experience for legitimate users.
Referral Fraud Detector
// lib/referral/fraud-detector.ts
import { db } from '../firebase/admin';
interface FraudSignal {
type: 'ip_duplicate' | 'device_fingerprint' | 'email_pattern' | 'timing_anomaly' | 'behavior_anomaly';
severity: 'low' | 'medium' | 'high';
description: string;
metadata: Record<string, any>;
}
interface FraudAnalysis {
isFraudulent: boolean;
riskScore: number; // 0-100
signals: FraudSignal[];
action: 'allow' | 'review' | 'block';
}
export class ReferralFraudDetector {
private static readonly RISK_THRESHOLDS = {
allow: 30,
review: 60,
block: 80
};
/**
* Analyze referral for fraud signals
*/
static async analyzeFraud(
refereeId: string,
referralCode: string,
metadata: {
ipAddress: string;
deviceFingerprint: string;
email: string;
userAgent: string;
}
): Promise<FraudAnalysis> {
const signals: FraudSignal[] = [];
// Check IP duplication
const ipSignal = await this.checkIPDuplication(
refereeId,
referralCode,
metadata.ipAddress
);
if (ipSignal) signals.push(ipSignal);
// Check device fingerprint
const deviceSignal = await this.checkDeviceFingerprint(
refereeId,
metadata.deviceFingerprint
);
if (deviceSignal) signals.push(deviceSignal);
// Check email patterns
const emailSignal = this.checkEmailPattern(metadata.email);
if (emailSignal) signals.push(emailSignal);
// Check timing anomalies
const timingSignal = await this.checkTimingAnomaly(referralCode);
if (timingSignal) signals.push(timingSignal);
// Calculate risk score
const riskScore = this.calculateRiskScore(signals);
// Determine action
let action: FraudAnalysis['action'] = 'allow';
if (riskScore >= this.RISK_THRESHOLDS.block) {
action = 'block';
} else if (riskScore >= this.RISK_THRESHOLDS.review) {
action = 'review';
}
const analysis: FraudAnalysis = {
isFraudulent: riskScore >= this.RISK_THRESHOLDS.review,
riskScore,
signals,
action
};
// Log fraud analysis
await db.collection('fraudAnalyses').add({
refereeId,
referralCode,
analysis,
analyzedAt: new Date()
});
return analysis;
}
/**
* Check for IP address duplication
*/
private static async checkIPDuplication(
refereeId: string,
referralCode: string,
ipAddress: string
): Promise<FraudSignal | null> {
// Get referrer ID from code
const codeDoc = await db.collection('referralCodes').doc(referralCode).get();
const referrerId = codeDoc.data()?.userId;
// Check if IP matches referrer's recent IPs
const referrerSessionsSnapshot = await db
.collection('userSessions')
.where('userId', '==', referrerId)
.where('ipAddress', '==', ipAddress)
.where('createdAt', '>', new Date(Date.now() - 86400000 * 7)) // Last 7 days
.get();
if (!referrerSessionsSnapshot.empty) {
return {
type: 'ip_duplicate',
severity: 'high',
description: 'Referee IP matches referrer IP within 7 days',
metadata: { ipAddress, referrerId }
};
}
return null;
}
/**
* Check device fingerprint duplication
*/
private static async checkDeviceFingerprint(
refereeId: string,
deviceFingerprint: string
): Promise<FraudSignal | null> {
const existingAccountsSnapshot = await db
.collection('userSessions')
.where('deviceFingerprint', '==', deviceFingerprint)
.where('userId', '!=', refereeId)
.get();
if (existingAccountsSnapshot.size > 2) {
return {
type: 'device_fingerprint',
severity: 'high',
description: `Device fingerprint matches ${existingAccountsSnapshot.size} other accounts`,
metadata: { deviceFingerprint, matchCount: existingAccountsSnapshot.size }
};
}
return null;
}
/**
* Check for suspicious email patterns
*/
private static checkEmailPattern(email: string): FraudSignal | null {
const suspiciousPatterns = [
/\+\d+@/, // Email aliases (user+1@example.com)
/@(tempmail|guerrillamail|10minutemail|mailinator)\./, // Disposable email domains
/\d{5,}@/, // Emails with 5+ consecutive digits
/^[a-z]{1,3}\d+@/ // Short prefix + numbers (a123@example.com)
];
for (const pattern of suspiciousPatterns) {
if (pattern.test(email)) {
return {
type: 'email_pattern',
severity: 'medium',
description: 'Email matches suspicious pattern',
metadata: { email, pattern: pattern.source }
};
}
}
return null;
}
/**
* Check for timing anomalies
*/
private static async checkTimingAnomaly(referralCode: string): Promise<FraudSignal | null> {
const recentAttributionsSnapshot = await db
.collection('referralAttributions')
.where('referralCode', '==', referralCode)
.where('firstTouchAt', '>', new Date(Date.now() - 60000)) // Last 1 minute
.get();
if (recentAttributionsSnapshot.size > 3) {
return {
type: 'timing_anomaly',
severity: 'high',
description: `${recentAttributionsSnapshot.size} referrals within 1 minute`,
metadata: { count: recentAttributionsSnapshot.size }
};
}
return null;
}
/**
* Calculate overall risk score
*/
private static calculateRiskScore(signals: FraudSignal[]): number {
const severityWeights = {
low: 15,
medium: 35,
high: 50
};
return signals.reduce((score, signal) => {
return score + severityWeights[signal.severity];
}, 0);
}
}
Duplicate Account Checker
// lib/referral/duplicate-checker.ts
import { db } from '../firebase/admin';
import { createHash } from 'crypto';
export class DuplicateAccountChecker {
/**
* Check for duplicate accounts using multiple signals
*/
static async checkDuplicates(
userId: string,
userData: {
email: string;
phoneNumber?: string;
paymentMethodFingerprint?: string;
}
): Promise<string[]> {
const duplicateUserIds = new Set<string>();
// Check email hash
const emailHash = this.hashPII(userData.email);
const emailMatches = await this.findMatchingUsers('emailHash', emailHash, userId);
emailMatches.forEach(id => duplicateUserIds.add(id));
// Check phone number hash
if (userData.phoneNumber) {
const phoneHash = this.hashPII(userData.phoneNumber);
const phoneMatches = await this.findMatchingUsers('phoneHash', phoneHash, userId);
phoneMatches.forEach(id => duplicateUserIds.add(id));
}
// Check payment method fingerprint
if (userData.paymentMethodFingerprint) {
const paymentMatches = await this.findMatchingUsers(
'paymentMethodFingerprint',
userData.paymentMethodFingerprint,
userId
);
paymentMatches.forEach(id => duplicateUserIds.add(id));
}
return Array.from(duplicateUserIds);
}
/**
* Hash PII for privacy-preserving comparisons
*/
private static hashPII(data: string): string {
return createHash('sha256').update(data.toLowerCase().trim()).digest('hex');
}
/**
* Find users with matching identifier
*/
private static async findMatchingUsers(
field: string,
value: string,
excludeUserId: string
): Promise<string[]> {
const snapshot = await db
.collection('users')
.where(field, '==', value)
.get();
return snapshot.docs
.map(doc => doc.id)
.filter(id => id !== excludeUserId);
}
}
Suspicious Pattern Analyzer
// lib/referral/pattern-analyzer.ts
import { db } from '../firebase/admin';
interface BehaviorPattern {
userId: string;
activityScore: number; // 0-100 (0 = no activity, 100 = highly active)
engagementMetrics: {
appsCreated: number;
loginFrequency: number;
featureUsage: number;
};
suspicionLevel: 'none' | 'low' | 'medium' | 'high';
}
export class SuspiciousPatternAnalyzer {
/**
* Analyze user behavior for suspicious patterns
*/
static async analyzeBehavior(userId: string): Promise<BehaviorPattern> {
const userDoc = await db.collection('users').doc(userId).get();
const userData = userDoc.data();
if (!userData) {
throw new Error('User not found');
}
// Calculate engagement metrics
const appsCreated = await this.getAppsCreatedCount(userId);
const loginFrequency = await this.getLoginFrequency(userId);
const featureUsage = await this.getFeatureUsageScore(userId);
const activityScore = this.calculateActivityScore({
appsCreated,
loginFrequency,
featureUsage
});
const suspicionLevel = this.determineSuspicionLevel(activityScore, userData);
return {
userId,
activityScore,
engagementMetrics: {
appsCreated,
loginFrequency,
featureUsage
},
suspicionLevel
};
}
/**
* Get number of apps created
*/
private static async getAppsCreatedCount(userId: string): Promise<number> {
const appsSnapshot = await db
.collection('apps')
.where('userId', '==', userId)
.get();
return appsSnapshot.size;
}
/**
* Calculate login frequency (logins per week)
*/
private static async getLoginFrequency(userId: string): Promise<number> {
const oneWeekAgo = new Date(Date.now() - 86400000 * 7);
const sessionsSnapshot = await db
.collection('userSessions')
.where('userId', '==', userId)
.where('createdAt', '>', oneWeekAgo)
.get();
return sessionsSnapshot.size;
}
/**
* Calculate feature usage score
*/
private static async getFeatureUsageScore(userId: string): Promise<number> {
const eventsSnapshot = await db
.collection('analyticsEvents')
.where('userId', '==', userId)
.where('eventCategory', 'in', ['app_edit', 'app_deploy', 'template_use'])
.get();
return Math.min(eventsSnapshot.size, 100);
}
/**
* Calculate overall activity score
*/
private static calculateActivityScore(metrics: {
appsCreated: number;
loginFrequency: number;
featureUsage: number;
}): number {
const weights = {
appsCreated: 0.4,
loginFrequency: 0.3,
featureUsage: 0.3
};
return Math.round(
metrics.appsCreated * 10 * weights.appsCreated +
metrics.loginFrequency * 5 * weights.loginFrequency +
metrics.featureUsage * weights.featureUsage
);
}
/**
* Determine suspicion level based on activity
*/
private static determineSuspicionLevel(
activityScore: number,
userData: any
): BehaviorPattern['suspicionLevel'] {
const accountAge = Date.now() - new Date(userData.createdAt).getTime();
const accountAgeDays = accountAge / 86400000;
// Zero activity after 7+ days = high suspicion
if (activityScore === 0 && accountAgeDays > 7) {
return 'high';
}
// Low activity after 14+ days = medium suspicion
if (activityScore < 20 && accountAgeDays > 14) {
return 'medium';
}
// Normal activity = no suspicion
if (activityScore > 40) {
return 'none';
}
return 'low';
}
}
Referral Analytics
Track and optimize referral program performance with actionable metrics.
K-Factor Calculator
// lib/referral/analytics.ts
export class ReferralAnalytics {
/**
* Calculate viral k-factor
* k = (number of invites per user) × (conversion rate)
* k > 1.0 = viral growth
*/
static async calculateKFactor(
startDate: Date,
endDate: Date
): Promise<number> {
// Get all conversions in period
const conversionsSnapshot = await db
.collection('referralConversions')
.where('timestamp', '>=', startDate)
.where('timestamp', '<=', endDate)
.get();
const totalConversions = conversionsSnapshot.size;
// Get unique referrers
const referrerIds = new Set(
conversionsSnapshot.docs.map(doc => doc.data().referrerId)
);
const totalReferrers = referrerIds.size;
// Get total shares in period
const sharesSnapshot = await db
.collection('referralShares')
.where('sharedAt', '>=', startDate)
.where('sharedAt', '<=', endDate)
.get();
const totalShares = sharesSnapshot.size;
// Calculate k-factor
const avgInvitesPerUser = totalShares / totalReferrers;
const conversionRate = totalConversions / totalShares;
const kFactor = avgInvitesPerUser * conversionRate;
return kFactor;
}
/**
* Get referral funnel metrics
*/
static async getFunnelMetrics(startDate: Date, endDate: Date) {
const shares = await db
.collection('referralShares')
.where('sharedAt', '>=', startDate)
.where('sharedAt', '<=', endDate)
.get();
const attributions = await db
.collection('referralAttributions')
.where('firstTouchAt', '>=', startDate)
.where('firstTouchAt', '<=', endDate)
.get();
const conversions = await db
.collection('referralConversions')
.where('timestamp', '>=', startDate)
.where('timestamp', '<=', endDate)
.get();
return {
totalShares: shares.size,
totalSignups: attributions.size,
totalConversions: conversions.size,
shareToSignupRate: (attributions.size / shares.size) * 100,
signupToConversionRate: (conversions.size / attributions.size) * 100,
overallConversionRate: (conversions.size / shares.size) * 100
};
}
}
Referral ROI Tracker
// lib/referral/roi-tracker.ts
export class ReferralROITracker {
/**
* Calculate referral program ROI
*/
static async calculateROI(startDate: Date, endDate: Date) {
// Get all rewards distributed
const rewardsSnapshot = await db
.collection('rewardDistributions')
.where('distributedAt', '>=', startDate)
.where('distributedAt', '<=', endDate)
.where('status', '==', 'completed')
.get();
const totalRewardsCost = rewardsSnapshot.docs.reduce(
(sum, doc) => sum + doc.data().amount,
0
);
// Get revenue from referred customers
const conversionsSnapshot = await db
.collection('referralConversions')
.where('timestamp', '>=', startDate)
.where('timestamp', '<=', endDate)
.get();
const totalReferralRevenue = conversionsSnapshot.docs.reduce(
(sum, doc) => sum + doc.data().conversionValue,
0
);
const roi = ((totalReferralRevenue - totalRewardsCost) / totalRewardsCost) * 100;
return {
totalRewardsCost,
totalReferralRevenue,
netProfit: totalReferralRevenue - totalRewardsCost,
roi: roi.toFixed(2) + '%',
paybackPeriod: totalRewardsCost / (totalReferralRevenue / 30) // Days to payback
};
}
}
Production Deployment Checklist
Before launching your referral program, validate these critical components:
System Configuration
- Referral code generator produces unique, collision-free codes
- Referral tracking captures attribution on signup
- Reward distribution integrates with billing system (Stripe/credits)
- Fraud detection runs on every referral submission
- Analytics tracking sends events to Google Analytics/Firebase
User Experience
- Share widget displays referral code and link
- One-click sharing to email, Twitter, LinkedIn, Facebook
- Email invites send personalized HTML emails
- Referral dashboard shows pending/completed referrals
Fraud Prevention
- IP duplication detection blocks self-referrals
- Device fingerprinting prevents multi-account abuse
- Email pattern detection flags disposable addresses
- Manual review queue for medium/high-risk referrals
Monitoring & Optimization
- K-factor tracking dashboard (target: k > 1.0)
- Referral funnel metrics (share → signup → conversion)
- ROI calculator measures program profitability
- A/B testing framework for reward amounts
Conclusion
A well-designed referral program transforms your ChatGPT app into a self-sustaining growth engine. The code examples in this guide provide production-ready implementations for referral code generation, attribution tracking, reward distribution, fraud detection, and viral loop optimization—everything you need to launch a referral program that drives 30-40% lower CAC and 2x better retention.
The key to referral success is reducing friction (make sharing effortless), preventing fraud (protect program integrity), and tracking metrics (optimize for k-factor > 1.0). Start with a simple two-sided incentive program ($25 credit for both referrer and referee), deploy the fraud detection system to block abuse, and measure k-factor weekly to identify optimization opportunities.
Ready to build a referral program that drives exponential growth? Start your free trial on MakeAIHQ and implement these referral strategies in your ChatGPT app today. Our platform includes built-in referral tracking, reward distribution, and fraud prevention—no additional coding required.
Internal Resources:
- ChatGPT App Monetization Guide - Complete monetization strategies
- Growth Hacking ChatGPT Apps - User acquisition tactics
- Viral Loop Design for ChatGPT Apps - Viral growth mechanics
- SaaS Growth Strategies - Enterprise growth playbooks
- Pricing Psychology for ChatGPT Apps - Conversion optimization
- Customer Retention Strategies - Reduce churn
- Email Marketing for ChatGPT Apps - Nurture campaigns
External Resources: