Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MatthewSabia1/Joip-Web-App-2/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The JOIP Referral Program rewards you for bringing new users to the platform. When someone signs up using your referral link:
  • You receive: 10 credits
  • They receive: 25 bonus credits
  • No limits: Refer as many friends as you want (subject to rate limiting)

How It Works

1. Get Your Referral Code

Every user automatically gets a unique 8-character referral code:
Format: ABCD1234
Example: K7M2P9X4
Characteristics:
  • 8 uppercase alphanumeric characters
  • Globally unique across all users
  • Generated on first account access
  • Never changes once created
Your referral link includes your encoded code:
https://joip.app?r=SzdNMlA5WDQ
The r parameter is your referral code encoded in base64url format.

3. New User Signs Up

When someone visits your referral link:
  1. Cookie is Set
    • System stores referral code in pending_referral cookie
    • Also stores referral_source (direct or session_share)
    • And referral_source_id if from shared session
  2. User Creates Account
    • Goes through normal signup/login flow
    • System detects pending referral cookie
  3. Referral Processed
    • Validates referral code
    • Checks for fraud (self-referral, duplicates)
    • Awards credits atomically in database transaction
    • Clears referral cookies

4. Credits Awarded

Both accounts receive credits instantly:
// Referrer (you)
+10 credits"Referral bonus - new user signup"

// Referred user (your friend)
+25 credits"Welcome bonus - referred by friend"
Transactions are logged in both users’ credit histories.

Referral Code Format

Code Structure

Raw Code:
K7M2P9X4
  • Length: Exactly 8 characters
  • Characters: A-Z and 0-9 (uppercase only)
  • Validation regex: /^[A-Z0-9]{8}$/
Encoded Code (for URLs):
SzdNMlA5WDQ
  • Format: Base64URL encoding
  • Safe for URL parameters
  • Decoded server-side for validation

Code Generation

Codes are generated using cryptographically random selection:
function generateCode(): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  return Array.from({ length: 8 }, () =>
    chars[Math.floor(Math.random() * chars.length)]
  ).join('');
}
Collision Handling:
  • Database unique constraint on users.referralCode
  • Automatic retry with new code on collision
  • Maximum 10 attempts before error
  • Collision probability: ~1 in 2.8 trillion

API Endpoints

Get Your Referral Stats

Get your referral statistics and earnings.Response:
{
  "referralCode": "K7M2P9X4",
  "totalReferrals": 5,
  "creditsEarned": 50,
  "referrerCredits": 10,
  "referredCredits": 25,
  "isProgramActive": true
}
Fields:
  • referralCode - Your unique code
  • totalReferrals - Number of successful referrals
  • creditsEarned - Total credits earned from referrals
  • referrerCredits - Credits per referral (configurable)
  • referredCredits - Credits new users receive (configurable)
  • isProgramActive - Whether program is currently active

Get Referral History

View your referral history with pagination.Query Parameters:
  • page - Page number (default: 1)
  • limit - Items per page (default: 10)
Response:
{
  "history": [
    {
      "id": 123,
      "creditsEarned": 10,
      "sourceType": "direct",
      "createdAt": "2026-03-01T10:30:00.000Z"
    }
  ],
  "hasMore": true,
  "total": 15
}
Privacy Note: For privacy reasons, referred user details (email, name) are not included in the response.

Validate Referral Code

Check if a referral code is valid (public endpoint).Request Body:
{
  "code": "K7M2P9X4"
}
Response:
{
  "valid": true
}
Note: Does not reveal the referrer’s identity for privacy.

Referral Sources

Referrals can come from two sources:

Direct Referrals

Source Type: direct When someone uses your referral link directly:
https://joip.app?r=SzdNMlA5WDQ
Cookie Set:
pending_referral=K7M2P9X4
referral_source=direct

Session Share Referrals

Source Type: session_share When someone discovers JOIP through a shared session:
https://joip.app/shared/abc123?r=SzdNMlA5WDQ
Cookies Set:
pending_referral=K7M2P9X4
referral_source=session_share
referral_source_id=abc123
Benefit:
  • Tracks which shared session brought in the user
  • Helps measure content virality
  • Same credit rewards as direct referrals

Fraud Prevention

Self-Referral Prevention

You cannot refer yourself:
if (referrerId === referredUserId) {
  return { success: false, error: 'Cannot refer yourself' };
}

Duplicate Prevention

Each user can only be referred once:
  • Database unique constraint on userReferrals.referredUserId
  • Check performed in transaction with row locking
  • users.referredBy field tracks referrer
Duplicate Detection:
// Check if user already has a referrer
if (user.referredBy !== null) {
  return { success: false, error: 'User already referred' };
}

// Also check referrals table
const existing = await db
  .select()
  .from(userReferrals)
  .where(eq(userReferrals.referredUserId, userId));

if (existing.length > 0) {
  return { success: false, error: 'User already referred' };
}

Rate Limiting

To prevent abuse, referrals are rate-limited: Default Limits:
  • Maximum 10 successful referrals per hour per referrer
  • Configurable via admin settings
Implementation:
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);

const recentReferrals = await db
  .select({ count: sql`count(*)` })
  .from(userReferrals)
  .where(
    and(
      eq(userReferrals.referrerId, referrerId),
      gte(userReferrals.createdAt, oneHourAgo)
    )
  );

if (recentReferrals[0].count >= maxPerHour) {
  return { success: false, error: 'Rate limit exceeded' };
}

Code Validation

Strict validation prevents malformed codes:
// Format validation
const REFERRAL_CODE_REGEX = /^[A-Z0-9]{8}$/;

function isValidCodeFormat(code: string): boolean {
  return REFERRAL_CODE_REGEX.test(code);
}

// Decode validation
function decodeReferralParam(encoded: string): string | null {
  // Length check
  if (encoded.length === 0 || encoded.length > 128) {
    return null;
  }
  
  // Decode
  try {
    const decoded = Buffer.from(encoded, 'base64url').toString('utf8');
    
    // Validate decoded format
    if (!isValidCodeFormat(decoded)) {
      return null;
    }
    
    return decoded;
  } catch {
    return null;
  }
}

Database Schema

Users Table

Referral-related fields:
CREATE TABLE users (
  id VARCHAR PRIMARY KEY,
  referral_code VARCHAR UNIQUE,     -- User's unique referral code
  referred_by VARCHAR,              -- ID of user who referred this user
  -- ... other fields
);

User Referrals Table

Tracks all successful referrals:
CREATE TABLE user_referrals (
  id SERIAL PRIMARY KEY,
  referrer_id VARCHAR NOT NULL REFERENCES users(id),
  referred_user_id VARCHAR NOT NULL UNIQUE REFERENCES users(id),
  referral_code VARCHAR NOT NULL,   -- Code used (for audit)
  credits_awarded INTEGER NOT NULL DEFAULT 10,
  bonus_credits_given INTEGER NOT NULL DEFAULT 25,
  source_type VARCHAR,              -- 'direct' | 'session_share'
  source_id VARCHAR,                -- Share code if from session
  created_at TIMESTAMP DEFAULT NOW(),
  
  CONSTRAINT unique_referred_user UNIQUE (referred_user_id)
);

CREATE INDEX idx_referrals_referrer ON user_referrals(referrer_id);
CREATE INDEX idx_referrals_created ON user_referrals(created_at);

Referral Settings Table

Admin-configurable settings:
CREATE TABLE referral_settings (
  id SERIAL PRIMARY KEY,
  referrer_credits INTEGER NOT NULL DEFAULT 10,
  referred_credits INTEGER NOT NULL DEFAULT 25,
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  max_referrals_per_hour INTEGER NOT NULL DEFAULT 10,
  updated_at TIMESTAMP DEFAULT NOW(),
  updated_by VARCHAR
);

Referral Processing Flow

Step-by-Step Process

Transaction Atomicity

The entire referral process happens in a single database transaction:
return await db.transaction(async (tx) => {
  // 1. Lock both users
  const lockedUsers = await tx
    .select()
    .from(users)
    .where(inArray(users.id, [referrerId, referredUserId]))
    .for('update');
  
  // 2. Validate
  if (referredUser.referredBy) {
    throw new Error('User already referred');
  }
  
  // 3. Check rate limit
  const recentCount = await tx
    .select({ count: sql`count(*)` })
    .from(userReferrals)
    .where(/* recent referrals */);
  
  if (recentCount >= maxPerHour) {
    throw new Error('Rate limit exceeded');
  }
  
  // 4. Insert referral record
  await tx.insert(userReferrals).values({
    referrerId,
    referredUserId,
    referralCode: code,
    creditsAwarded: 10,
    bonusCreditsGiven: 25,
    sourceType,
    sourceId
  });
  
  // 5. Update user's referredBy
  await tx
    .update(users)
    .set({ referredBy: referrerId })
    .where(eq(users.id, referredUserId));
  
  // 6. Award credits (using same transaction)
  await creditService.addCredits(
    referrerId, 10, 'bonus',
    'Referral bonus - new user signup',
    undefined,
    tx // Pass transaction
  );
  
  await creditService.addCredits(
    referredUserId, 25, 'bonus',
    'Welcome bonus - referred by friend',
    undefined,
    tx // Pass transaction
  );
  
  return { success: true };
});
Benefits:
  • Either everything succeeds or nothing happens
  • No partial referrals
  • No lost credits
  • No race conditions

Admin Features

View Referral Statistics

Get system-wide referral statistics (admin only).Response:
{
  "totalReferrals": 1250,
  "totalCreditsAwarded": 12500,
  "totalBonusCreditsGiven": 31250,
  "settings": {
    "referrerCredits": 10,
    "referredCredits": 25,
    "isActive": true,
    "maxReferralsPerHour": 10
  }
}

Update Referral Settings

Update referral program configuration (admin only).Request Body:
{
  "referrerCredits": 15,
  "referredCredits": 30,
  "isActive": true,
  "maxReferralsPerHour": 5
}
Response: Returns updated settings object.Note: Changes affect future referrals only. Existing referrals are not retroactively adjusted.

View Recent Referrals

Get recent referrals with user details (admin only).Query Parameters:
  • limit - Number of results (default: 50)
Response:
[
  {
    "id": 123,
    "referrerId": "user-abc",
    "referredUserId": "user-xyz",
    "referralCode": "K7M2P9X4",
    "creditsAwarded": 10,
    "bonusCreditsGiven": 25,
    "sourceType": "direct",
    "createdAt": "2026-03-01T10:30:00.000Z",
    "referrerEmail": "referrer@example.com",
    "referredEmail": "newuser@example.com"
  }
]

Best Practices

For Users

  1. Share Responsibly
    • Only share with genuinely interested people
    • Don’t spam or use deceptive tactics
    • Explain what JOIP is before sharing link
  2. Track Your Success
    • Check /api/referrals/stats regularly
    • Monitor which sharing methods work best
    • Use session shares to showcase content
  3. Maximize Referrals
    • Share your best sessions publicly
    • Include referral link in social media bios
    • Engage with community to build trust

For Developers

  1. Always Validate Codes
    const decoded = referralService.decodeReferralParam(encodedCode);
    if (!decoded) {
      // Invalid code - don't set cookie
      return;
    }
    
  2. Set Secure Cookies
    res.cookie('pending_referral', code, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 7 * 24 * 60 * 60 * 1000 // 1 week
    });
    
  3. Clear Cookies After Processing
    const clearPendingReferralCookies = (res) => {
      const cookieOptions = { path: '/', sameSite: 'lax' };
      res.clearCookie('pending_referral', cookieOptions);
      res.clearCookie('referral_source', cookieOptions);
      res.clearCookie('referral_source_id', cookieOptions);
    };
    
  4. Log Referral Events
    if (result.success) {
      logger.info(
        `Referral processed: ${referredUserId} referred by ${referrerId}. ` +
        `Referrer gets ${result.referrerCredits}, ` +
        `referred gets ${result.referredCredits} credits.`
      );
    }
    

Troubleshooting

Possible Causes:
  • User already had an account
  • Self-referral attempt
  • Invalid referral code
  • Rate limit exceeded
  • Referral program disabled
Debug Steps:
  1. Check server logs for referral processing errors
  2. Verify referral code exists: SELECT * FROM users WHERE referral_code = 'CODE'
  3. Check if user already referred: SELECT referred_by FROM users WHERE id = 'USER_ID'
  4. Verify program is active: SELECT is_active FROM referral_settings
Possible Causes:
  • Credits initialization failed
  • Transaction rollback due to error
  • Database connection issue
Debug Steps:
  1. Check if referral record exists: SELECT * FROM user_referrals WHERE referred_user_id = 'USER_ID'
  2. Check credit transactions: SELECT * FROM credit_transactions WHERE user_id IN ('REFERRER_ID', 'REFERRED_ID')
  3. Review server logs for transaction errors
Possible Causes:
  • Code format incorrect (must be 8 chars, A-Z0-9)
  • Code doesn’t exist in database
  • Encoding/decoding error
Solution:
// Validate format first
if (!/^[A-Z0-9]{8}$/.test(code)) {
  throw new Error('Invalid code format');
}

// Then check existence
const { valid, referrerId } = await referralService.validateReferralCode(code);
if (!valid) {
  throw new Error('Code not found');
}
Cause: Referrer has made 10+ successful referrals in the past hourSolution:
  • Wait for rate limit window to pass (1 hour)
  • Admin can adjust limit: UPDATE referral_settings SET max_referrals_per_hour = 20
Check current rate:
SELECT COUNT(*)
FROM user_referrals
WHERE referrer_id = 'USER_ID'
  AND created_at >= NOW() - INTERVAL '1 hour';

Analytics & Tracking

Referral Metrics

Track these metrics to measure program success:
  1. Conversion Rate
    (Successful Referrals / Total Referral Link Clicks) × 100
    
  2. Credits Per User
    Total Credits Awarded / Total Referrers
    
  3. Source Distribution
    SELECT source_type, COUNT(*) as count
    FROM user_referrals
    GROUP BY source_type;
    
  4. Top Referrers
    SELECT referrer_id, COUNT(*) as referrals, SUM(credits_awarded) as total_credits
    FROM user_referrals
    GROUP BY referrer_id
    ORDER BY referrals DESC
    LIMIT 10;
    

Activity Logging

Referral events are logged in the activity system:
await storage.logUserActivity({
  userId: referredUserId,
  action: 'referral_signup',
  feature: 'referrals',
  details: {
    referrerId,
    referralCode,
    sourceType,
    creditsEarned: referredCredits
  },
  ipAddress: req.ip,
  userAgent: req.get('User-Agent'),
  sessionId: req.sessionID
});

Future Enhancements

Planned improvements to the referral system:
  1. Tiered Rewards
    • More credits for milestone referrals (10, 25, 50, 100)
    • Bonus rewards for high-quality referrals (active users)
  2. Referral Contests
    • Monthly leaderboards
    • Prizes for top referrers
  3. Custom Referral Codes
    • Allow users to set vanity codes
    • Validation for uniqueness and appropriateness
  4. Referral Dashboard
    • Visual analytics
    • Referral timeline
    • Conversion funnel
  5. Social Sharing Integration
    • One-click sharing to Twitter, Discord, Reddit
    • Pre-written share messages
    • Referral link shortening

Build docs developers (and LLMs) love