Partner Revenue Sharing for ChatGPT Apps: Affiliate Management
Building a ChatGPT app is only half the battle—getting it into users' hands requires strategic distribution partnerships. Partner revenue sharing programs transform your affiliates, influencers, and strategic partners into a powerful sales force. With the ChatGPT App Store reaching 800 million weekly users, implementing a robust affiliate management system can accelerate your growth exponentially while rewarding those who champion your app.
This comprehensive guide covers everything from commission models and tracking infrastructure to partner portals and payout automation. Whether you're launching your first affiliate program or scaling an existing partnership network, you'll learn production-ready strategies for building a revenue sharing ecosystem that drives sustainable growth. We'll explore tiered commission structures, cross-device attribution, automated payouts, and partner recruitment tactics that top SaaS companies use to build multi-million dollar channel programs.
The key to successful partner revenue sharing isn't just tracking referrals—it's creating a frictionless system that motivates partners, provides transparency, and automates administrative overhead. Let's build an affiliate management system that turns your partners into your most valuable growth engine.
Commission Models
Choosing the right commission structure directly impacts partner motivation and program economics. Different models suit different business objectives and partner types.
Percentage-Based Commissions
Percentage-based commissions scale with transaction value, aligning partner incentives with revenue growth:
// commission-calculator.ts - Production-grade commission engine
import Stripe from 'stripe';
import { Firestore } from '@google-cloud/firestore';
interface CommissionTier {
name: string;
minReferrals: number;
percentage: number;
recurringMonths: number;
}
interface CommissionCalculation {
partnerId: string;
orderId: string;
orderValue: number;
commissionAmount: number;
tierApplied: string;
isRecurring: boolean;
recurringPayments: number;
}
export class CommissionCalculator {
private db: Firestore;
private stripe: Stripe;
// Commission tiers based on performance
private tiers: CommissionTier[] = [
{ name: 'Bronze', minReferrals: 0, percentage: 20, recurringMonths: 3 },
{ name: 'Silver', minReferrals: 10, percentage: 25, recurringMonths: 6 },
{ name: 'Gold', minReferrals: 50, percentage: 30, recurringMonths: 12 },
{ name: 'Platinum', minReferrals: 100, percentage: 35, recurringMonths: 24 }
];
constructor(db: Firestore, stripeKey: string) {
this.db = db;
this.stripe = new Stripe(stripeKey, { apiVersion: '2023-10-16' });
}
async calculateCommission(
partnerId: string,
orderId: string,
orderValue: number
): Promise<CommissionCalculation> {
// Get partner's performance tier
const tier = await this.getPartnerTier(partnerId);
// Calculate base commission
const commissionAmount = orderValue * (tier.percentage / 100);
// Determine if this is a subscription (recurring)
const order = await this.getOrderDetails(orderId);
const isRecurring = order.type === 'subscription';
return {
partnerId,
orderId,
orderValue,
commissionAmount,
tierApplied: tier.name,
isRecurring,
recurringPayments: isRecurring ? tier.recurringMonths : 1
};
}
private async getPartnerTier(partnerId: string): Promise<CommissionTier> {
const partnerRef = this.db.collection('partners').doc(partnerId);
const partnerDoc = await partnerRef.get();
if (!partnerDoc.exists) {
throw new Error(`Partner ${partnerId} not found`);
}
const totalReferrals = partnerDoc.data()?.totalReferrals || 0;
// Find applicable tier (descending order)
for (let i = this.tiers.length - 1; i >= 0; i--) {
if (totalReferrals >= this.tiers[i].minReferrals) {
return this.tiers[i];
}
}
return this.tiers[0]; // Default to Bronze
}
private async getOrderDetails(orderId: string): Promise<any> {
const orderRef = this.db.collection('orders').doc(orderId);
const orderDoc = await orderRef.get();
if (!orderDoc.exists) {
throw new Error(`Order ${orderId} not found`);
}
return orderDoc.data();
}
async recordCommission(calculation: CommissionCalculation): Promise<void> {
const commissionRef = this.db.collection('commissions').doc();
await commissionRef.set({
...calculation,
status: 'pending',
createdAt: new Date(),
paidAt: null,
paymentId: null
});
// Update partner stats
await this.updatePartnerStats(calculation.partnerId, calculation.commissionAmount);
}
private async updatePartnerStats(partnerId: string, amount: number): Promise<void> {
const partnerRef = this.db.collection('partners').doc(partnerId);
await this.db.runTransaction(async (transaction) => {
const partnerDoc = await transaction.get(partnerRef);
const currentEarnings = partnerDoc.data()?.totalEarnings || 0;
const currentReferrals = partnerDoc.data()?.totalReferrals || 0;
transaction.update(partnerRef, {
totalEarnings: currentEarnings + amount,
totalReferrals: currentReferrals + 1,
updatedAt: new Date()
});
});
}
// Process recurring commission payments
async processRecurringCommission(
originalCommissionId: string,
paymentNumber: number
): Promise<void> {
const originalRef = this.db.collection('commissions').doc(originalCommissionId);
const originalDoc = await originalRef.get();
if (!originalDoc.exists) {
throw new Error('Original commission not found');
}
const original = originalDoc.data() as CommissionCalculation;
// Only process if within recurring period
if (paymentNumber <= original.recurringPayments) {
const recurringRef = this.db.collection('commissions').doc();
await recurringRef.set({
partnerId: original.partnerId,
orderId: original.orderId,
orderValue: original.orderValue,
commissionAmount: original.commissionAmount,
tierApplied: original.tierApplied,
isRecurring: true,
recurringPaymentNumber: paymentNumber,
originalCommissionId,
status: 'pending',
createdAt: new Date()
});
await this.updatePartnerStats(original.partnerId, original.commissionAmount);
}
}
}
This calculator handles tiered commissions, recurring payments, and automatic tier upgrades based on performance.
Flat-Rate and Hybrid Models
Flat-rate commissions provide predictability, while hybrid models combine both approaches:
// hybrid-commission-model.ts
interface HybridCommissionConfig {
baseFlat: number;
percentageBonus: number;
minimumPayout: number;
maximumPayout: number;
}
export class HybridCommissionModel {
private config: HybridCommissionConfig = {
baseFlat: 50, // $50 base per conversion
percentageBonus: 10, // Plus 10% of order value
minimumPayout: 50,
maximumPayout: 500
};
calculate(orderValue: number): number {
const flatAmount = this.config.baseFlat;
const percentageAmount = orderValue * (this.config.percentageBonus / 100);
const totalCommission = flatAmount + percentageAmount;
// Apply caps
return Math.max(
this.config.minimumPayout,
Math.min(totalCommission, this.config.maximumPayout)
);
}
}
Learn more about ChatGPT app monetization strategies and subscription management for ChatGPT apps.
Affiliate Tracking
Accurate tracking is the foundation of any revenue sharing program. Modern affiliate systems must handle cross-device journeys, multiple touchpoints, and attribution windows.
Cookie-Based Attribution
// affiliate-tracker.ts - Production tracking system
import { Request, Response } from 'express';
import { Firestore, Timestamp } from '@google-cloud/firestore';
import crypto from 'crypto';
interface AffiliateClick {
clickId: string;
partnerId: string;
userId?: string;
timestamp: Timestamp;
ip: string;
userAgent: string;
referrer: string;
landingPage: string;
device: 'mobile' | 'desktop' | 'tablet';
campaign?: string;
}
interface ConversionEvent {
conversionId: string;
clickId: string;
partnerId: string;
userId: string;
orderId: string;
orderValue: number;
timestamp: Timestamp;
attributionModel: 'first-touch' | 'last-touch' | 'linear';
}
export class AffiliateTracker {
private db: Firestore;
private cookieName = 'aff_ref';
private cookieDuration = 30 * 24 * 60 * 60 * 1000; // 30 days
constructor(db: Firestore) {
this.db = db;
}
// Track affiliate click
async trackClick(req: Request, res: Response, partnerId: string): Promise<string> {
const clickId = this.generateClickId();
const click: AffiliateClick = {
clickId,
partnerId,
timestamp: Timestamp.now(),
ip: this.getClientIP(req),
userAgent: req.get('user-agent') || 'unknown',
referrer: req.get('referer') || 'direct',
landingPage: req.query.landing as string || '/',
device: this.detectDevice(req.get('user-agent') || ''),
campaign: req.query.campaign as string
};
// Store click in database
await this.db.collection('affiliate_clicks').doc(clickId).set(click);
// Set tracking cookie
res.cookie(this.cookieName, JSON.stringify({
clickId,
partnerId,
timestamp: Date.now()
}), {
maxAge: this.cookieDuration,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax'
});
return clickId;
}
// Track conversion event
async trackConversion(
userId: string,
orderId: string,
orderValue: number,
req: Request
): Promise<ConversionEvent | null> {
// Get affiliate cookie
const affiliateCookie = req.cookies[this.cookieName];
if (!affiliateCookie) {
return null; // No affiliate attribution
}
const { clickId, partnerId, timestamp } = JSON.parse(affiliateCookie);
// Check if within attribution window
const clickAge = Date.now() - timestamp;
if (clickAge > this.cookieDuration) {
return null; // Attribution expired
}
const conversionId = this.generateConversionId();
const conversion: ConversionEvent = {
conversionId,
clickId,
partnerId,
userId,
orderId,
orderValue,
timestamp: Timestamp.now(),
attributionModel: 'last-touch'
};
// Store conversion
await this.db.collection('conversions').doc(conversionId).set(conversion);
// Update click record
await this.db.collection('affiliate_clicks').doc(clickId).update({
converted: true,
conversionId,
userId
});
return conversion;
}
// Multi-touch attribution for complex journeys
async trackMultiTouchConversion(
userId: string,
orderId: string,
orderValue: number
): Promise<ConversionEvent[]> {
// Get all clicks for this user
const clicksSnapshot = await this.db
.collection('affiliate_clicks')
.where('userId', '==', userId)
.where('timestamp', '>', Timestamp.fromMillis(Date.now() - this.cookieDuration))
.orderBy('timestamp', 'asc')
.get();
const clicks = clicksSnapshot.docs.map(doc => doc.data() as AffiliateClick);
if (clicks.length === 0) {
return [];
}
// Linear attribution: split commission equally
const commissionPerClick = orderValue / clicks.length;
const conversions: ConversionEvent[] = [];
for (const click of clicks) {
const conversionId = this.generateConversionId();
const conversion: ConversionEvent = {
conversionId,
clickId: click.clickId,
partnerId: click.partnerId,
userId,
orderId,
orderValue: commissionPerClick,
timestamp: Timestamp.now(),
attributionModel: 'linear'
};
await this.db.collection('conversions').doc(conversionId).set(conversion);
conversions.push(conversion);
}
return conversions;
}
private generateClickId(): string {
return `clk_${crypto.randomBytes(16).toString('hex')}`;
}
private generateConversionId(): string {
return `conv_${crypto.randomBytes(16).toString('hex')}`;
}
private getClientIP(req: Request): string {
return (
(req.headers['x-forwarded-for'] as string)?.split(',')[0] ||
req.socket.remoteAddress ||
'unknown'
);
}
private detectDevice(userAgent: string): 'mobile' | 'desktop' | 'tablet' {
const ua = userAgent.toLowerCase();
if (/tablet|ipad/.test(ua)) return 'tablet';
if (/mobile|android|iphone/.test(ua)) return 'mobile';
return 'desktop';
}
// Generate partner-specific tracking link
generateTrackingLink(partnerId: string, campaign?: string): string {
const baseUrl = process.env.APP_URL || 'https://makeaihq.com';
const params = new URLSearchParams({
ref: partnerId,
...(campaign && { campaign })
});
return `${baseUrl}/?${params.toString()}`;
}
}
This tracker handles cookie-based attribution, multi-touch attribution, and device detection across the user journey.
Partner Portal
A self-service partner portal empowers affiliates with real-time analytics, marketing resources, and payout transparency:
// partner-portal.tsx - React component for partner dashboard
import React, { useState, useEffect } from 'react';
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';
import { Firestore } from '@google-cloud/firestore';
interface PartnerStats {
totalClicks: number;
totalConversions: number;
conversionRate: number;
totalEarnings: number;
pendingPayouts: number;
paidOut: number;
currentTier: string;
nextTierReferrals: number;
}
interface PerformanceData {
date: string;
clicks: number;
conversions: number;
earnings: number;
}
export const PartnerPortal: React.FC<{ partnerId: string }> = ({ partnerId }) => {
const [stats, setStats] = useState<PartnerStats | null>(null);
const [performanceData, setPerformanceData] = useState<PerformanceData[]>([]);
const [trackingLinks, setTrackingLinks] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadPartnerData();
}, [partnerId]);
const loadPartnerData = async () => {
setLoading(true);
// Fetch partner stats
const statsData = await fetchPartnerStats(partnerId);
setStats(statsData);
// Fetch performance history (last 30 days)
const performanceHistory = await fetchPerformanceHistory(partnerId, 30);
setPerformanceData(performanceHistory);
// Generate tracking links
const links = generateTrackingLinks(partnerId);
setTrackingLinks(links);
setLoading(false);
};
const fetchPartnerStats = async (partnerId: string): Promise<PartnerStats> => {
// In production, this would call your API
const response = await fetch(`/api/partners/${partnerId}/stats`);
return response.json();
};
const fetchPerformanceHistory = async (
partnerId: string,
days: number
): Promise<PerformanceData[]> => {
const response = await fetch(`/api/partners/${partnerId}/performance?days=${days}`);
return response.json();
};
const generateTrackingLinks = (partnerId: string): string[] => {
return [
`https://makeaihq.com/?ref=${partnerId}`,
`https://makeaihq.com/pricing?ref=${partnerId}&campaign=pricing`,
`https://makeaihq.com/templates?ref=${partnerId}&campaign=templates`
];
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
alert('Tracking link copied to clipboard!');
};
if (loading) {
return <div className="loading">Loading partner dashboard...</div>;
}
return (
<div className="partner-portal">
<header className="portal-header">
<h1>Partner Dashboard</h1>
<div className="tier-badge">{stats?.currentTier} Partner</div>
</header>
<div className="stats-grid">
<div className="stat-card">
<h3>Total Earnings</h3>
<div className="stat-value">${stats?.totalEarnings.toLocaleString()}</div>
<div className="stat-meta">
Pending: ${stats?.pendingPayouts.toLocaleString()}
</div>
</div>
<div className="stat-card">
<h3>Conversions</h3>
<div className="stat-value">{stats?.totalConversions}</div>
<div className="stat-meta">
{stats?.conversionRate.toFixed(2)}% conversion rate
</div>
</div>
<div className="stat-card">
<h3>Clicks</h3>
<div className="stat-value">{stats?.totalClicks.toLocaleString()}</div>
<div className="stat-meta">
{stats?.nextTierReferrals} more for next tier
</div>
</div>
<div className="stat-card">
<h3>Paid Out</h3>
<div className="stat-value">${stats?.paidOut.toLocaleString()}</div>
</div>
</div>
<div className="performance-charts">
<h2>Performance Trends</h2>
<div className="chart-container">
<h3>Earnings Over Time</h3>
<LineChart width={600} height={300} data={performanceData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="earnings" stroke="#D4AF37" name="Earnings ($)" />
</LineChart>
</div>
<div className="chart-container">
<h3>Clicks vs Conversions</h3>
<BarChart width={600} height={300} data={performanceData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="clicks" fill="#0A0E27" name="Clicks" />
<Bar dataKey="conversions" fill="#D4AF37" name="Conversions" />
</BarChart>
</div>
</div>
<div className="tracking-links">
<h2>Your Tracking Links</h2>
{trackingLinks.map((link, index) => (
<div key={index} className="link-item">
<input type="text" value={link} readOnly />
<button onClick={() => copyToClipboard(link)}>Copy</button>
</div>
))}
</div>
<div className="marketing-resources">
<h2>Marketing Resources</h2>
<div className="resource-grid">
<a href="/partners/banners" className="resource-card">Banner Ads</a>
<a href="/partners/email-templates" className="resource-card">Email Templates</a>
<a href="/partners/social-media" className="resource-card">Social Media Kit</a>
<a href="/partners/case-studies" className="resource-card">Case Studies</a>
</div>
</div>
</div>
);
};
Explore analytics for ChatGPT apps and user onboarding for ChatGPT apps.
Payout Automation
Automating payouts reduces administrative overhead while ensuring partners get paid on time:
// payout-manager.ts - Automated payout processing
import Stripe from 'stripe';
import { Firestore, Timestamp } from '@google-cloud/firestore';
interface PayoutSchedule {
frequency: 'weekly' | 'biweekly' | 'monthly';
minimumThreshold: number;
nextPayoutDate: Date;
}
interface PayoutBatch {
batchId: string;
partnerId: string;
amount: number;
commissionIds: string[];
status: 'pending' | 'processing' | 'completed' | 'failed';
createdAt: Timestamp;
processedAt?: Timestamp;
stripeTransferId?: string;
}
export class PayoutManager {
private db: Firestore;
private stripe: Stripe;
private defaultSchedule: PayoutSchedule = {
frequency: 'monthly',
minimumThreshold: 100, // $100 minimum payout
nextPayoutDate: this.getNextPayoutDate('monthly')
};
constructor(db: Firestore, stripeKey: string) {
this.db = db;
this.stripe = new Stripe(stripeKey, { apiVersion: '2023-10-16' });
}
// Generate payout batches for all partners
async generatePayoutBatches(): Promise<PayoutBatch[]> {
const partnersSnapshot = await this.db.collection('partners').get();
const batches: PayoutBatch[] = [];
for (const partnerDoc of partnersSnapshot.docs) {
const partnerId = partnerDoc.id;
const schedule = partnerDoc.data().payoutSchedule || this.defaultSchedule;
// Check if it's time for payout
if (new Date() < schedule.nextPayoutDate) {
continue;
}
// Get pending commissions
const commissionsSnapshot = await this.db
.collection('commissions')
.where('partnerId', '==', partnerId)
.where('status', '==', 'pending')
.get();
const commissions = commissionsSnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
// Calculate total amount
const totalAmount = commissions.reduce(
(sum, commission) => sum + commission.commissionAmount,
0
);
// Check minimum threshold
if (totalAmount < schedule.minimumThreshold) {
continue;
}
// Create payout batch
const batch: PayoutBatch = {
batchId: this.generateBatchId(),
partnerId,
amount: totalAmount,
commissionIds: commissions.map(c => c.id),
status: 'pending',
createdAt: Timestamp.now()
};
await this.db.collection('payout_batches').doc(batch.batchId).set(batch);
batches.push(batch);
}
return batches;
}
// Process payout batch via Stripe
async processPayout(batchId: string): Promise<void> {
const batchRef = this.db.collection('payout_batches').doc(batchId);
const batchDoc = await batchRef.get();
if (!batchDoc.exists) {
throw new Error(`Payout batch ${batchId} not found`);
}
const batch = batchDoc.data() as PayoutBatch;
try {
// Update status to processing
await batchRef.update({ status: 'processing' });
// Get partner's Stripe Connect account
const partnerDoc = await this.db
.collection('partners')
.doc(batch.partnerId)
.get();
const stripeAccountId = partnerDoc.data()?.stripeConnectAccountId;
if (!stripeAccountId) {
throw new Error('Partner has no Stripe Connect account');
}
// Create Stripe transfer
const transfer = await this.stripe.transfers.create({
amount: Math.round(batch.amount * 100), // Convert to cents
currency: 'usd',
destination: stripeAccountId,
description: `Affiliate payout - Batch ${batchId}`
});
// Update batch and commissions
await this.db.runTransaction(async (transaction) => {
// Mark batch as completed
transaction.update(batchRef, {
status: 'completed',
processedAt: Timestamp.now(),
stripeTransferId: transfer.id
});
// Mark commissions as paid
for (const commissionId of batch.commissionIds) {
const commissionRef = this.db.collection('commissions').doc(commissionId);
transaction.update(commissionRef, {
status: 'paid',
paidAt: Timestamp.now(),
paymentBatchId: batchId
});
}
});
// Update partner's next payout date
const schedule = partnerDoc.data()?.payoutSchedule || this.defaultSchedule;
await this.db.collection('partners').doc(batch.partnerId).update({
'payoutSchedule.nextPayoutDate': this.getNextPayoutDate(schedule.frequency),
totalPaidOut: (partnerDoc.data()?.totalPaidOut || 0) + batch.amount
});
} catch (error) {
// Mark batch as failed
await batchRef.update({
status: 'failed',
errorMessage: (error as Error).message,
processedAt: Timestamp.now()
});
throw error;
}
}
private getNextPayoutDate(frequency: 'weekly' | 'biweekly' | 'monthly'): Date {
const now = new Date();
const next = new Date(now);
switch (frequency) {
case 'weekly':
next.setDate(now.getDate() + 7);
break;
case 'biweekly':
next.setDate(now.getDate() + 14);
break;
case 'monthly':
next.setMonth(now.getMonth() + 1);
next.setDate(1); // First of next month
break;
}
return next;
}
private generateBatchId(): string {
return `batch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Generate tax report for partners
async generateTaxReport(partnerId: string, year: number): Promise<any> {
const startDate = new Date(year, 0, 1);
const endDate = new Date(year, 11, 31, 23, 59, 59);
const commissionsSnapshot = await this.db
.collection('commissions')
.where('partnerId', '==', partnerId)
.where('status', '==', 'paid')
.where('paidAt', '>=', Timestamp.fromDate(startDate))
.where('paidAt', '<=', Timestamp.fromDate(endDate))
.get();
const totalEarnings = commissionsSnapshot.docs.reduce(
(sum, doc) => sum + doc.data().commissionAmount,
0
);
return {
partnerId,
year,
totalEarnings,
paymentCount: commissionsSnapshot.size,
generatedAt: new Date()
};
}
}
Review payment processing for ChatGPT apps and Stripe integration for ChatGPT apps.
Partner Recruitment
Building a high-performing partner network requires strategic recruitment and relationship management:
// partner-recruitment.ts - Automated recruitment system
import { Firestore, Timestamp } from '@google-cloud/firestore';
import nodemailer from 'nodemailer';
interface PartnerApplication {
applicationId: string;
email: string;
name: string;
website?: string;
socialMedia: {
twitter?: string;
linkedin?: string;
youtube?: string;
};
audience: string;
estimatedReach: number;
experience: string;
status: 'pending' | 'approved' | 'rejected';
submittedAt: Timestamp;
}
export class PartnerRecruitment {
private db: Firestore;
private emailer: nodemailer.Transporter;
constructor(db: Firestore, emailConfig: any) {
this.db = db;
this.emailer = nodemailer.createTransport(emailConfig);
}
async submitApplication(data: Omit<PartnerApplication, 'applicationId' | 'status' | 'submittedAt'>): Promise<string> {
const applicationId = this.generateApplicationId();
const application: PartnerApplication = {
applicationId,
...data,
status: 'pending',
submittedAt: Timestamp.now()
};
await this.db.collection('partner_applications').doc(applicationId).set(application);
// Send confirmation email
await this.sendApplicationConfirmation(application);
return applicationId;
}
async approveApplication(applicationId: string): Promise<void> {
const appRef = this.db.collection('partner_applications').doc(applicationId);
const appDoc = await appRef.get();
if (!appDoc.exists) {
throw new Error('Application not found');
}
const application = appDoc.data() as PartnerApplication;
// Create partner account
const partnerId = await this.createPartnerAccount(application);
// Update application status
await appRef.update({
status: 'approved',
partnerId,
approvedAt: Timestamp.now()
});
// Send welcome email with credentials
await this.sendWelcomeEmail(application, partnerId);
}
private async createPartnerAccount(application: PartnerApplication): Promise<string> {
const partnerId = this.generatePartnerId();
await this.db.collection('partners').doc(partnerId).set({
partnerId,
email: application.email,
name: application.name,
website: application.website,
socialMedia: application.socialMedia,
tier: 'Bronze',
totalReferrals: 0,
totalEarnings: 0,
totalPaidOut: 0,
createdAt: Timestamp.now()
});
return partnerId;
}
private async sendApplicationConfirmation(application: PartnerApplication): Promise<void> {
await this.emailer.sendMail({
from: 'partners@makeaihq.com',
to: application.email,
subject: 'Partner Application Received',
html: `
<h1>Thank you for applying!</h1>
<p>Hi ${application.name},</p>
<p>We've received your application to join the MakeAIHQ Partner Program.</p>
<p>Our team will review your application and get back to you within 2-3 business days.</p>
`
});
}
private async sendWelcomeEmail(application: PartnerApplication, partnerId: string): Promise<void> {
const loginUrl = `https://partners.makeaihq.com/login`;
await this.emailer.sendMail({
from: 'partners@makeaihq.com',
to: application.email,
subject: 'Welcome to MakeAIHQ Partner Program!',
html: `
<h1>Welcome ${application.name}!</h1>
<p>Congratulations! Your application has been approved.</p>
<p><strong>Your Partner ID:</strong> ${partnerId}</p>
<p><strong>Login:</strong> <a href="${loginUrl}">${loginUrl}</a></p>
<h2>Next Steps:</h2>
<ul>
<li>Login to your partner dashboard</li>
<li>Set up your payment details</li>
<li>Generate your tracking links</li>
<li>Download marketing materials</li>
</ul>
<p>Questions? Reply to this email or visit our Partner FAQ.</p>
`
});
}
private generateApplicationId(): string {
return `app_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private generatePartnerId(): string {
return `partner_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
Database Schema
Complete schema for partner revenue sharing:
-- partners table
CREATE TABLE partners (
partner_id VARCHAR(255) PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
website VARCHAR(500),
stripe_connect_account_id VARCHAR(255),
tier VARCHAR(50) DEFAULT 'Bronze',
total_referrals INTEGER DEFAULT 0,
total_earnings DECIMAL(10, 2) DEFAULT 0,
total_paid_out DECIMAL(10, 2) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_tier (tier)
);
-- affiliate_clicks table
CREATE TABLE affiliate_clicks (
click_id VARCHAR(255) PRIMARY KEY,
partner_id VARCHAR(255) NOT NULL,
user_id VARCHAR(255),
ip_address VARCHAR(45),
user_agent TEXT,
referrer VARCHAR(500),
landing_page VARCHAR(500),
device VARCHAR(50),
campaign VARCHAR(255),
converted BOOLEAN DEFAULT FALSE,
conversion_id VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (partner_id) REFERENCES partners(partner_id),
INDEX idx_partner (partner_id),
INDEX idx_user (user_id),
INDEX idx_created (created_at)
);
-- commissions table
CREATE TABLE commissions (
commission_id VARCHAR(255) PRIMARY KEY,
partner_id VARCHAR(255) NOT NULL,
order_id VARCHAR(255) NOT NULL,
order_value DECIMAL(10, 2) NOT NULL,
commission_amount DECIMAL(10, 2) NOT NULL,
tier_applied VARCHAR(50),
is_recurring BOOLEAN DEFAULT FALSE,
recurring_payment_number INTEGER,
status VARCHAR(50) DEFAULT 'pending',
paid_at TIMESTAMP,
payment_batch_id VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (partner_id) REFERENCES partners(partner_id),
INDEX idx_partner_status (partner_id, status),
INDEX idx_status (status),
INDEX idx_created (created_at)
);
-- payout_batches table
CREATE TABLE payout_batches (
batch_id VARCHAR(255) PRIMARY KEY,
partner_id VARCHAR(255) NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
commission_count INTEGER NOT NULL,
status VARCHAR(50) DEFAULT 'pending',
stripe_transfer_id VARCHAR(255),
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP,
FOREIGN KEY (partner_id) REFERENCES partners(partner_id),
INDEX idx_partner (partner_id),
INDEX idx_status (status)
);
See also database schema design for ChatGPT apps and Firestore security rules for ChatGPT apps.
Conclusion
Partner revenue sharing transforms passive affiliates into active growth drivers. By implementing tiered commissions, accurate attribution, self-service portals, and automated payouts, you create a partnership ecosystem that scales alongside your ChatGPT app business.
The key differentiators are transparency and automation—partners who can track their performance in real-time and receive payouts automatically are more engaged and motivated. Whether you're starting with a handful of influencers or building a network of hundreds of partners, the infrastructure we've covered provides the foundation for sustainable, profitable growth.
Ready to build a partner revenue sharing program? Start your free trial with MakeAIHQ and deploy ChatGPT apps with built-in affiliate tracking, commission management, and partner portals—no coding required.
For deeper insights on monetization strategies, explore our guides on freemium models for ChatGPT apps, tiered pricing strategies, and ROI tracking for ChatGPT apps.
Related Resources: