Skip to content

Eneza - Product Requirements Document ​

Africa's First WhatsApp Status Advertising Platform

Connecting brands with millions of engaged WhatsApp users across Africa

Status: 🟢 LIVE (Production) Version: 2.0 Users: 12,000+ Live URL: https://api.eneza.app / https://eneza.appLast Updated: March 2026


Table of Contents ​

  1. Vision & Problem Statement
  2. Solution Overview
  3. Core Features
  4. User Journeys
  5. Data Models
  6. API Reference
  7. Service Architecture
  8. Authentication & Authorization
  9. Billing System
  10. Verification System
  11. Notifications
  12. Analytics & Reporting
  13. Technical Stack
  14. Roadmap
  15. Gaps & Needed Improvements

1. Vision & Problem Statement ​

The Problem ​

  1. For Advertisers: Traditional digital advertising in Africa is:

    • Expensive (CPM rates don't match African spending power)
    • Low trust (banner blindness, ad fatigue)
    • Poor targeting (users aren't the engaged audience)
    • Difficult to verify (impressions ≠ actual views)
  2. For African Youth:

    • High unemployment rates (40%+ in many African countries)
    • WhatsApp is ubiquitous (700M+ users in Africa)
    • No easy way to monetize their social influence
    • Traditional gig economy platforms exclude Africa

The Vision ​

Eneza ("spread" in Swahili) creates Africa's first peer-to-peer WhatsApp status advertising network where:

  • Advertisers pay for verified, real human views on WhatsApp statuses
  • Posters/Publishers earn money by posting ads to their WhatsApp status for 24 hours
  • AI-powered verification ensures authenticity and prevents fraud

Think Instagram Influencer Marketing meets Gig Economy — but designed for Africa's most popular app: WhatsApp.

Business Model ​

┌─────────────────────┐         ┌─────────────────────┐
│    ADVERTISERS      │         │   POSTERS (Users)   │
│  (Brands/Agencies)  │         │  (WhatsApp Users)   │
└─────────┬───────────┘         └───────────┬─────────┘
          │                                 │
          │ Pay per verified view           │ Earn per verified view
          │ ($0.01 - $0.05/view)            │ (60-70% of ad revenue)
          │                                 │
          └─────────────┬───────────────────┘
                        │
              ┌─────────▼─────────┐
              │      ENEZA       │
              │   (Platform Fee   │
              │    30-40%)        │
              └───────────────────┘

2. Solution Overview ​

How It Works ​

┌──────────────────────────────────────────────────────────────────────────────────┐
│                           ENEZA WORKFLOW                                         │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  1. ADVERTISER                    2. POSTER                    3. VERIFICATION  │
│  ═══════════                      ═══════                      ══════════════   │
│                                                                                  │
│  ┌─────────────┐                  ┌─────────────┐              ┌─────────────┐  │
│  │ Create Ad   │                  │ Subscribe   │              │ AI Verifies │  │
│  │ Campaign    │──────────────────▶│ to Campaign │──────────────▶│ Screenshot  │  │
│  │ + Video     │                  │             │              │             │  │
│  └─────────────┘                  └─────────────┘              └─────────────┘  │
│         │                                │                            │          │
│         │                                │                            │          │
│  ┌─────────────┐                  ┌─────────────┐              ┌─────────────┐  │
│  │ Fund with   │                  │ Download    │              │ Screenshot  │  │
│  │ Credits     │                  │ Personalized│              │ Passes AI   │  │
│  │ (Stripe)    │                  │ Ad Video    │              │ Checks?     │  │
│  └─────────────┘                  └─────────────┘              └─────────────┘  │
│                                          │                            │          │
│                                          │                            │          │
│                                   ┌─────────────┐              ┌─────────────┐  │
│                                   │ Post to     │              │ View Count  │  │
│                                   │ WhatsApp    │              │ Extracted   │  │
│                                   │ Status      │              │ by Gemini   │  │
│                                   └─────────────┘              └─────────────┘  │
│                                          │                            │          │
│                                          │                            │          │
│                                   ┌─────────────┐              ┌─────────────┐  │
│                                   │ Wait 20-24  │              │ Payout      │  │
│                                   │ Hours       │──────────────▶│ Calculated  │  │
│                                   │             │              │ & Queued    │  │
│                                   └─────────────┘              └─────────────┘  │
│                                          │                            │          │
│                                          │                            │          │
│                                   ┌─────────────┐              ┌─────────────┐  │
│                                   │ Upload      │              │ Mobile      │  │
│                                   │ Screenshot  │              │ Money       │  │
│                                   │ of Views    │              │ Withdrawal  │  │
│                                   └─────────────┘              └─────────────┘  │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

Key Differentiators ​

FeatureTraditional AdsEneza
View VerificationSelf-reportedAI-verified screenshots
Audience TrustLow (strangers)High (friends/contacts)
Fraud DetectionBasicML-powered multi-check pipeline
PersonalizationGenericQR-coded watermarked videos
PayoutN/AReal-time mobile money
Geographic FocusGlobalAfrica-first

3. Core Features ​

3.1 For Advertisers ​

Campaign Management ​

  • Create Ad Campaigns with video/image content (up to 200MB)
  • Set Budget in USD with guaranteed view counts
  • Target Countries and demographics
  • Multiple Video Variants (portrait/landscape, different resolutions)
  • Campaign Scheduling with start/end dates
  • Real-time Analytics dashboard

Billing & Deposits ​

  • Stripe Integration for credit card payments
  • Credit System (USD-based ad credits)
  • Volume Discounts on large deposits
  • Invoice Generation for accounting
  • Deposit History and transaction tracking

Analytics ​

  • View Counts (total, verified, pending)
  • Geographic Distribution of views
  • Subscription Rates by campaign
  • Cost per Verified View calculations
  • Fraud Detection Metrics

3.2 For Posters (Publishers) ​

Ad Discovery & Subscription ​

  • Browse Available Ads with earnings potential
  • Subscribe to Campaigns (one at a time, max based on tier)
  • Download Personalized Video with unique QR watermark
  • 24-Hour Posting Window with automatic expiry

Screenshot Verification ​

  • Upload Screenshot of WhatsApp status views
  • AI-Powered Verification (Gemini Vision)
  • Multi-Check Pipeline (QR code, image matching, view count extraction)
  • Real-time Verification Status updates

Earnings & Payouts ​

  • Per-View Earnings (varies by campaign, country, tier)
  • Wallet Balance tracking in USD + local currency
  • Mobile Money Withdrawals (MTN MoMo, Airtel Money, etc.)
  • Transaction History with status tracking
  • Minimum Withdrawal thresholds by country

3.3 Growth Engine (Gamification) ​

Referral System ​

  • Unique Referral Codes for each user
  • Referral Bonuses ($0.25-$0.50 per successful referral)
  • Referral Tracking with friend activity feed

Tier System ​

TierReferralsEarnings Multiplier
Bronze0-41.0x
Silver5-141.1x (+10%)
Gold15-291.2x (+20%)
Platinum30-491.3x (+30%)
Diamond50+1.5x (+50%)

Achievement System ​

  • First Referral (Bonus: $0.50)
  • Social Butterfly (5 referrals, $1.00)
  • Influencer (10 referrals, $2.50)
  • Community Builder (25 referrals, $5.00)
  • Ambassador (50 referrals, $10.00)
  • Legend (100 referrals, $25.00)

Streak System ​

  • Daily Activity Tracking
  • Streak Multipliers (+5% to +25% based on streak length)
  • Streak Shields (protects against missed days)
  • Milestone Achievements (7, 14, 30, 60, 90, 180 days)

3.4 Admin Dashboard ​

  • User Management (ban/unban, fraud review)
  • Campaign Moderation (approve/reject ads)
  • Transaction Oversight (approve withdrawals)
  • Analytics Dashboard (platform-wide metrics)
  • Fraud Detection Reports
  • Support Ticket Management

4. User Journeys ​

4.1 Advertiser Journey ​

┌────────────────────────────────────────────────────────────────────────────────┐
│                        ADVERTISER JOURNEY                                      │
├────────────────────────────────────────────────────────────────────────────────┤
│                                                                                │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Sign Up  │────▶│  Verify   │────▶│  Deposit  │────▶│  Create   │        │
│   │  Email    │     │  Email    │     │  Credits  │     │  Campaign │        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│                                                                │               │
│                                                                ▼               │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Review   │◀────│  Monitor  │◀────│  Campaign │◀────│  Upload   │        │
│   │  Results  │     │  Live     │     │  Goes     │     │  Video    │        │
│   │           │     │  Stats    │     │  Live     │     │           │        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│                                                                                │
│   Key Metrics:                                                                 │
│   - Views: Total | Verified | Pending                                          │
│   - Subscriptions: Active | Completed | Expired                                │
│   - Spend: Total USD | Cost per View                                           │
│   - Geographic: Views by Country                                               │
│                                                                                │
└────────────────────────────────────────────────────────────────────────────────┘

Key Touchpoints ​

  1. Registration: Email + password + company details
  2. Email Verification: Required before depositing
  3. Stripe Checkout: Secure payment with Stripe
  4. Campaign Creation: Upload video, set budget, choose countries
  5. Campaign Activation: Automatic when budget is funded
  6. Monitoring: Real-time dashboard with live stats
  7. Invoices: PDF invoices for accounting

4.2 Poster (Publisher) Journey ​

┌────────────────────────────────────────────────────────────────────────────────┐
│                          POSTER JOURNEY                                        │
├────────────────────────────────────────────────────────────────────────────────┤
│                                                                                │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Sign Up  │────▶│  Verify   │────▶│  Browse   │────▶│ Subscribe │        │
│   │  (Phone)  │     │  OTP      │     │  Ads      │     │  to Ad    │        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│                                                                │               │
│                                                                ▼               │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Upload   │────▶│  Wait for │────▶│  Post to  │◀────│  Download │        │
│   │ Screenshot│     │ Verifi-   │     │  WhatsApp │     │ Personal- │        │
│   │           │     │ cation    │     │  Status   │     │ ized Video│        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│        │                                                                       │
│        ▼                                                                       │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐                           │
│   │  View     │────▶│  Request  │────▶│  Receive  │                           │
│   │  Earnings │     │ Withdrawal│     │  Mobile   │                           │
│   │           │     │           │     │  Money    │                           │
│   └───────────┘     └───────────┘     └───────────┘                           │
│                                                                                │
│   Earnings Formula:                                                            │
│   Base Rate × View Count × Tier Multiplier × Streak Multiplier                 │
│                                                                                │
│   Example: $0.002/view × 50 views × 1.2 (Gold) × 1.1 (Streak) = $0.132         │
│                                                                                │
└────────────────────────────────────────────────────────────────────────────────┘

Key Touchpoints ​

  1. Registration: Phone number + OTP verification
  2. Country Selection: Determines available campaigns and payout methods
  3. Profile Setup: Date of birth validation
  4. Ad Browsing: Filter by category, earnings potential
  5. Subscription: Get unique subscription code
  6. Video Download: Personalized with QR watermark
  7. WhatsApp Posting: User posts to their status
  8. Wait Period: 20-24 hours minimum
  9. Screenshot Upload: Must show view count
  10. Verification: AI pipeline processes screenshot
  11. Payout: View count × rate = earnings
  12. Withdrawal: Request mobile money payout

4.3 Admin Journey ​

┌────────────────────────────────────────────────────────────────────────────────┐
│                           ADMIN JOURNEY                                        │
├────────────────────────────────────────────────────────────────────────────────┤
│                                                                                │
│   ┌───────────────────────────────────────────────────────────────────────┐   │
│   │                      DAILY OPERATIONS                                  │   │
│   └───────────────────────────────────────────────────────────────────────┘   │
│                                                                                │
│   ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐        │
│   │  Review   │     │  Approve  │     │  Monitor  │     │  Handle   │        │
│   │  Fraud    │     │  With-    │     │  Platform │     │  Support  │        │
│   │  Alerts   │     │  drawals  │     │  Health   │     │  Tickets  │        │
│   └───────────┘     └───────────┘     └───────────┘     └───────────┘        │
│                                                                                │
│   ┌───────────────────────────────────────────────────────────────────────┐   │
│   │                      MODERATION                                        │   │
│   └───────────────────────────────────────────────────────────────────────┘   │
│                                                                                │
│   - Review flagged screenshots (NEEDS_REVIEW status)                           │
│   - Manually approve/reject verification results                               │
│   - Ban users with repeated fraud attempts                                     │
│   - Moderate ad content before going live                                      │
│                                                                                │
└────────────────────────────────────────────────────────────────────────────────┘

5. Data Models ​

5.1 Core Tables ​

User (Posters/Publishers) ​

prisma
model User {
  id                 String              @id @default(uuid())
  phoneNumber        String              @unique
  dob                DateTime?
  countryId          String?
  paymentProcessorId String?
  accountNumber      String?
  referralCode       String?             @unique
  referredBy         String?             // ID of referring user
  
  // Verification
  isVerified         Boolean             @default(false)
  isPremium          Boolean             @default(false)
  isBlocked          Boolean             @default(false)
  blockReason        String?
  
  // Push Notifications
  fcmToken           String?
  
  // Stats (denormalized for performance)
  totalEarnings      Decimal             @default(0)
  availableBalance   Decimal             @default(0)
  pendingBalance     Decimal             @default(0)
  
  // Growth Engine
  referralTierId     String?
  
  // Timestamps
  createdAt          DateTime            @default(now())
  updatedAt          DateTime            @updatedAt
  
  // Relations
  country            Country?            @relation(...)
  paymentProcessor   PaymentProcessor?   @relation(...)
  subscriptions      Subscription[]
  transactions       Transaction[]
  screenshots        Screenshot[]
  userStats          UserStats?
  userStreak         UserStreak?
  userAchievements   UserAchievement[]
  referralTier       ReferralTier?       @relation(...)
  referredUsers      User[]              @relation("UserReferrals")
  referrer           User?               @relation("UserReferrals")
}

Advertiser ​

prisma
model Advertiser {
  id               String          @id @default(uuid())
  email            String          @unique
  password         String
  contactPerson    String
  companyName      String?
  businessAddress  String?
  industry         String?
  
  // Verification
  isVerified       Boolean         @default(false)
  emailVerified    Boolean         @default(false)
  status           AdvertiserStatus @default(PENDING)
  
  // Billing (Stripe)
  stripeCustomerId String?
  
  // Credits & Balance
  credits          Decimal         @default(0) // USD credits
  totalSpent       Decimal         @default(0)
  
  // Timestamps
  createdAt        DateTime        @default(now())
  updatedAt        DateTime        @updatedAt
  
  // Relations
  ads              Ad[]
  deposits         Deposit[]
  invoices         Invoice[]
  supportTickets   SupportTicket[]
}

enum AdvertiserStatus {
  PENDING
  ACTIVE
  SUSPENDED
  BANNED
}

Ad (Campaign) ​

prisma
model Ad {
  id                 String         @id @default(uuid())
  advertiserId       String
  title              String
  description        String?
  categoryId         String?
  shortCode          String         @unique // e.g., "AD-ABC123"
  
  // Media
  video              String?        // Primary video URL
  videoVariants      Json?          // Multiple resolution variants
  adThumbnail        String?
  adImage            String?
  
  // Budget & Pricing
  budgetUsd          Decimal        @default(0)
  guaranteedViews    Int            @default(0)
  costPerViewUsd     Decimal?       // Calculated
  
  // Country Targeting
  targetCountries    Json?          // Array of country IDs
  
  // Status & Scheduling
  active             Boolean        @default(false)
  status             AdStatus       @default(DRAFT)
  startDate          DateTime?
  endDate            DateTime?
  
  // Timestamps
  createdAt          DateTime       @default(now())
  updatedAt          DateTime       @updatedAt
  
  // Relations
  advertiser         Advertiser     @relation(...)
  category           Category?      @relation(...)
  subscriptions      Subscription[]
  adStats            AdStats?
  adPricing          AdPricing[]
}

enum AdStatus {
  DRAFT
  PENDING_APPROVAL
  ACTIVE
  PAUSED
  COMPLETED
  CANCELLED
  REJECTED
}

Subscription (User's subscription to an ad) ​

prisma
model Subscription {
  id              String              @id @default(uuid())
  userId          String
  adId            String
  code            String              @unique // Unique subscription code for QR
  
  // Status
  isActive        Boolean             @default(true)
  status          SubscriptionStatus  @default(ACTIVE)
  
  // Device Info (for fraud detection)
  phoneInfo       Json?
  ipAddress       String?
  ipCountry       String?
  
  // Personalized Video
  personalizedVideoUrl String?
  personalizedVideoStatus VideoProcessingStatus?
  
  // Timestamps
  subscribeTime   DateTime            @default(now())
  downloadedAt    DateTime?
  expiresAt       DateTime?
  completedAt     DateTime?
  createdAt       DateTime            @default(now())
  updatedAt       DateTime            @updatedAt
  
  // Relations
  user            User                @relation(...)
  ad              Ad                  @relation(...)
  screenshots     Screenshot[]
}

enum SubscriptionStatus {
  ACTIVE
  COMPLETED
  EXPIRED
  CANCELLED
  REJECTED
  FAILED
}

enum VideoProcessingStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
}

Screenshot (Verification submission) ​

prisma
model Screenshot {
  id                  String              @id @default(uuid())
  userId              String
  subscriptionId      String
  adId                String
  
  // Image Data
  screenshotLink      String?             // R2/S3 URL
  processedImageUrl   String?             // Processed version
  
  // User-submitted data
  submittedViews      Int?
  
  // Verification Results
  status              ScreenshotStatus    @default(PENDING)
  verificationScore   Decimal?            // 0.0 - 1.0
  verificationResult  Json?               // Full verification details
  extractedViews      Int?                // AI-extracted view count
  
  // Payout
  payoutAmount        Decimal?
  payoutCurrency      String?
  payoutStatus        PayoutStatus?
  
  // Fraud Detection
  fraudScore          Decimal?
  fraudFlags          Json?
  isManualReview      Boolean             @default(false)
  
  // Timestamps
  submissionTime      DateTime            @default(now())
  verifiedAt          DateTime?
  processedAt         DateTime?
  createdAt           DateTime            @default(now())
  updatedAt           DateTime            @updatedAt
  
  // Relations
  user                User                @relation(...)
  subscription        Subscription        @relation(...)
  ad                  Ad                  @relation(...)
}

enum ScreenshotStatus {
  PENDING
  PROCESSING
  VERIFIED
  REJECTED
  NEEDS_REVIEW
  FAILED
  FRAUD
}

enum PayoutStatus {
  PENDING
  QUEUED
  PROCESSING
  COMPLETED
  FAILED
}

5.2 Financial Tables ​

Transaction ​

prisma
model Transaction {
  id                    String            @id @default(uuid())
  userId                String
  type                  TransactionType
  status                TransactionStatus @default(PENDING)
  
  // Amounts
  amountUsd             Decimal
  amountLocal           Decimal?
  currencyCode          String            @default("USD")
  exchangeRate          Decimal?
  
  // Payment Details
  paymentProcessorId    String?
  paymentProcessorRef   String?           // External reference
  accountNumber         String?
  phoneNumber           String?
  
  // References
  screenshotId          String?
  subscriptionId        String?
  adId                  String?
  
  // Metadata
  description           String?
  notes                 String?
  failureReason         String?
  
  // Timestamps
  processedAt           DateTime?
  createdAt             DateTime          @default(now())
  updatedAt             DateTime          @updatedAt
  
  // Relations
  user                  User              @relation(...)
  paymentProcessor      PaymentProcessor? @relation(...)
  screenshot            Screenshot?       @relation(...)
}

enum TransactionType {
  EARNING           // From verified screenshots
  WITHDRAWAL        // Cash out to mobile money
  REFERRAL_BONUS    // From referring users
  ACHIEVEMENT_BONUS // From achievements
  ADJUSTMENT        // Manual adjustments
  REFUND
}

enum TransactionStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
  CANCELLED
  ON_HOLD
}

Deposit (Advertiser deposits) ​

prisma
model Deposit {
  id                 String          @id @default(uuid())
  depositNumber      String          @unique // e.g., "DEP-2026-0001"
  advertiserId       String
  
  // Amounts
  amountUsd          Decimal
  bonusPercent       Int?            // Volume discount percentage
  bonusAmount        Decimal?
  totalCredits       Decimal         // amountUsd + bonusAmount
  
  // Stripe
  stripePaymentIntentId  String?
  stripeCheckoutSessionId String?
  
  // Status
  status             DepositStatus   @default(PENDING)
  
  // Metadata
  description        String?
  
  // Timestamps
  completedAt        DateTime?
  createdAt          DateTime        @default(now())
  updatedAt          DateTime        @updatedAt
  
  // Relations
  advertiser         Advertiser      @relation(...)
}

enum DepositStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
  REFUNDED
}

Invoice ​

prisma
model Invoice {
  id                String         @id @default(uuid())
  invoiceNumber     String         @unique // e.g., "INV-2026-0001"
  advertiserId      String
  depositId         String?
  
  // Amounts
  subtotalUsd       Decimal
  taxUsd            Decimal        @default(0)
  totalUsd          Decimal
  
  // Status
  status            InvoiceStatus  @default(DRAFT)
  
  // PDF
  pdfUrl            String?
  
  // Dates
  issueDate         DateTime       @default(now())
  dueDate           DateTime?
  paidAt            DateTime?
  
  // Timestamps
  createdAt         DateTime       @default(now())
  updatedAt         DateTime       @updatedAt
  
  // Relations
  advertiser        Advertiser     @relation(...)
  deposit           Deposit?       @relation(...)
  items             InvoiceItem[]
}

enum InvoiceStatus {
  DRAFT
  SENT
  PAID
  OVERDUE
  CANCELLED
}

5.3 Growth Engine Tables ​

UserStats ​

prisma
model UserStats {
  id                     String    @id @default(uuid())
  userId                 String    @unique
  
  // Screenshot Stats
  totalScreenshots       Int       @default(0)
  approvedScreenshots    Int       @default(0)
  rejectedScreenshots    Int       @default(0)
  pendingScreenshots     Int       @default(0)
  
  // View Stats
  totalVerifiedViews     Int       @default(0)
  
  // Referral Stats
  totalReferrals         Int       @default(0)
  completedReferrals     Int       @default(0) // Referrals who earned
  
  // Earnings Stats
  totalEarningsUsd       Decimal   @default(0)
  totalWithdrawalsUsd    Decimal   @default(0)
  totalReferralBonusUsd  Decimal   @default(0)
  
  // Leaderboard
  weeklyRank             Int?
  monthlyRank            Int?
  allTimeRank            Int?
  
  // Timestamps
  updatedAt              DateTime  @updatedAt
  
  // Relations
  user                   User      @relation(...)
}

UserStreak ​

prisma
model UserStreak {
  id                 String    @id @default(uuid())
  userId             String    @unique
  
  currentStreak      Int       @default(0)
  longestStreak      Int       @default(0)
  totalDaysActive    Int       @default(0)
  
  lastActivityDate   DateTime?
  streakShields      Int       @default(0) // Protect against missed days
  shieldUsedAt       DateTime?
  
  // Timestamps
  createdAt          DateTime  @default(now())
  updatedAt          DateTime  @updatedAt
  
  // Relations
  user               User      @relation(...)
}

ReferralTier ​

prisma
model ReferralTier {
  id                  String   @id @default(uuid())
  name                String   @unique // Bronze, Silver, Gold, Platinum, Diamond
  minReferrals        Int
  maxReferrals        Int?     // null = unlimited
  earningsMultiplier  Decimal  @default(1.0) // 1.1 = +10%
  badgeColor          String   // Hex color
  description         String?
  sortOrder           Int
  isActive            Boolean  @default(true)
  
  // Relations
  users               User[]
}

Achievement ​

prisma
model Achievement {
  id           String   @id @default(uuid())
  key          String   @unique // e.g., "first_referral"
  name         String
  description  String
  category     String   // referral, streak, earning, view
  requirement  Int      // Number required to unlock
  bonusUsd     Decimal  // Bonus awarded
  badgeColor   String
  iconUrl      String?
  sortOrder    Int
  isActive     Boolean  @default(true)
  
  // Relations
  userAchievements UserAchievement[]
}

UserAchievement ​

prisma
model UserAchievement {
  id            String      @id @default(uuid())
  userId        String
  achievementId String
  
  progress      Int         @default(0) // Current progress
  completed     Boolean     @default(false)
  completedAt   DateTime?
  bonusClaimed  Boolean     @default(false)
  claimedAt     DateTime?
  
  // Timestamps
  createdAt     DateTime    @default(now())
  updatedAt     DateTime    @updatedAt
  
  // Relations
  user          User        @relation(...)
  achievement   Achievement @relation(...)
  
  @@unique([userId, achievementId])
}

5.4 Configuration Tables ​

Country ​

prisma
model Country {
  id                  String   @id @default(uuid())
  name                String
  code                String   @unique // ISO 3166-1 alpha-2
  callingCode         String   // e.g., "+254"
  currencyCode        String   // e.g., "KES"
  currencySymbol      String   // e.g., "KSh"
  phoneNumberLength   Int?     // Expected phone number length
  minimumWithdrawal   Decimal  // Minimum withdrawal in USD
  isActive            Boolean  @default(true)
  isWaitlistOnly      Boolean  @default(false)
  
  // Relations
  users               User[]
  paymentProcessors   PaymentProcessor[]
  cities              City[]
  adPricing           AdPricing[]
}

PaymentProcessor ​

prisma
model PaymentProcessor {
  id           String   @id @default(uuid())
  name         String   // e.g., "MTN MoMo"
  code         String   @unique
  type         String   // MOBILE_MONEY, BANK_TRANSFER
  countryId    String
  
  isActive     Boolean  @default(true)
  
  // API Configuration (encrypted)
  apiKey       String?
  apiSecret    String?
  apiEndpoint  String?
  
  // Relations
  country      Country  @relation(...)
  users        User[]
  transactions Transaction[]
}

Category ​

prisma
model Category {
  id          String   @id @default(uuid())
  name        String
  description String?
  iconUrl     String?
  color       String?
  sortOrder   Int      @default(0)
  isActive    Boolean  @default(true)
  
  // Relations
  ads         Ad[]
}

5.5 Support Tables ​

SupportTicket ​

prisma
model SupportTicket {
  id           String         @id @default(uuid())
  ticketNumber String         @unique // e.g., "TKT-2026-0001"
  
  // Submitter (one of)
  userId       String?
  advertiserId String?
  
  // Content
  subject      String
  description  String
  category     String?        // billing, technical, fraud, other
  priority     Priority       @default(MEDIUM)
  status       TicketStatus   @default(OPEN)
  
  // Assignment
  assignedTo   String?        // Admin ID
  
  // Timestamps
  resolvedAt   DateTime?
  createdAt    DateTime       @default(now())
  updatedAt    DateTime       @updatedAt
  
  // Relations
  user         User?          @relation(...)
  advertiser   Advertiser?    @relation(...)
  messages     TicketMessage[]
}

enum Priority {
  LOW
  MEDIUM
  HIGH
  URGENT
}

enum TicketStatus {
  OPEN
  IN_PROGRESS
  WAITING_ON_USER
  RESOLVED
  CLOSED
}

5.6 Analytics Tables ​

AdStats (Denormalized for performance) ​

prisma
model AdStats {
  id                   String   @id @default(uuid())
  adId                 String   @unique
  
  // Subscription Counts
  subscriptionCount    Int      @default(0)
  activeSubscriptions  Int      @default(0)
  completedSubscriptions Int    @default(0)
  
  // View Counts
  totalViews           Int      @default(0)
  verifiedViews        Int      @default(0)
  balanceViews         Int      @default(0) // Views from balance (after deductions)
  estimatedExpectedViews Int    @default(0)
  
  // Screenshot Counts
  screenshotUploadCount Int     @default(0)
  screenshotApprovedCount Int   @default(0)
  screenshotRejectedCount Int   @default(0)
  
  // Download Counts
  downloadCount        Int      @default(0)
  
  // Timestamps
  updatedAt            DateTime @updatedAt
  
  // Relations
  ad                   Ad       @relation(...)
}

6. API Reference ​

6.1 Authentication Endpoints ​

User (Poster) Auth ​

POST /auth/signup                    # Register with phone + OTP
POST /auth/login                     # Login with phone + OTP
POST /auth/verify-otp                # Verify OTP code
POST /auth/resend-otp                # Resend OTP
POST /auth/logout                    # Logout (invalidate token)
POST /auth/refresh-token             # Refresh JWT

Advertiser Auth ​

POST /advertisers/signup             # Register with email
POST /advertisers/login              # Login with email + password
POST /advertisers/verify-email       # Verify email token
POST /advertisers/forgot-password    # Password reset request
POST /advertisers/reset-password     # Reset password with token

Admin Auth ​

POST /admin/auth/login               # Admin login
POST /admin/auth/logout              # Admin logout
GET  /admin/auth/me                  # Get current admin

6.2 Ad Endpoints ​

# Public
GET  /ads                            # List active ads (for posters)
GET  /ads/:id                        # Get ad details
GET  /ads/shortcode/:shortCode       # Get ad by short code

# Authenticated (Advertisers)
POST /advertisers/ads                # Create new ad
PUT  /advertisers/ads/:id            # Update ad
DELETE /advertisers/ads/:id          # Delete ad
POST /advertisers/ads/:id/upload-video   # Upload video
GET  /advertisers/ads/:id/stats      # Get ad statistics

# Admin
GET  /admin/ads                      # List all ads
PUT  /admin/ads/:id/approve          # Approve ad
PUT  /admin/ads/:id/reject           # Reject ad

6.3 Subscription Endpoints ​

# Authenticated (Users)
POST /subscriptions                  # Subscribe to ad
GET  /subscriptions                  # List my subscriptions
GET  /subscriptions/:id              # Get subscription details
GET  /subscriptions/:id/status       # Check subscription status
DELETE /subscriptions/:id            # Cancel subscription

# Video Download
GET  /subscriptions/:id/video        # Get personalized video URL
GET  /subscriptions/:id/video/status # Check video processing status

6.4 Screenshot Endpoints ​

# Authenticated (Users)
POST /screenshots                    # Submit screenshot
GET  /screenshots                    # List my screenshots
GET  /screenshots/:id                # Get screenshot details
GET  /screenshots/:id/status         # Check verification status

# Admin
GET  /admin/screenshots              # List all screenshots
GET  /admin/screenshots/pending      # Pending review
PUT  /admin/screenshots/:id/approve  # Manual approve
PUT  /admin/screenshots/:id/reject   # Manual reject

6.5 Transaction Endpoints ​

# Authenticated (Users)
GET  /transactions                   # List my transactions
GET  /transactions/:id               # Get transaction details
POST /transactions/withdraw          # Request withdrawal
GET  /transactions/balance           # Get wallet balance

# Admin
GET  /admin/transactions             # List all transactions
PUT  /admin/transactions/:id/approve # Approve withdrawal
PUT  /admin/transactions/:id/reject  # Reject withdrawal

6.6 Deposit Endpoints (Advertisers) ​

# Authenticated (Advertisers)
POST /api/deposits                   # Create deposit (returns Stripe checkout URL)
GET  /api/deposits                   # List my deposits
GET  /api/deposits/:id               # Get deposit details

# Stripe Webhook
POST /api/deposits/webhook/stripe    # Stripe webhook handler

6.7 Referral Endpoints ​

# Authenticated (Users)
GET  /referrals/code                 # Get my referral code
POST /referrals/validate             # Validate a referral code
GET  /referrals/stats                # Get referral statistics
GET  /referrals/history              # List referred users
GET  /referrals/friend-activity      # Friend activity feed
GET  /referrals/leaderboard          # Referral leaderboard
GET  /referrals/achievements         # My achievements
GET  /referrals/streaks/status       # Streak status
POST /referrals/streaks/use-shield   # Use streak shield
GET  /referrals/tiers                # Tier information
GET  /referrals/social-proof         # Platform stats (public)
GET  /referrals/dashboard            # Combined dashboard data

6.8 Support Endpoints ​

# Authenticated (Users & Advertisers)
POST /support-tickets                # Create ticket
GET  /support-tickets                # List my tickets
GET  /support-tickets/:id            # Get ticket details
POST /support-tickets/:id/messages   # Add message

# Admin
GET  /admin/support-tickets          # List all tickets
PUT  /admin/support-tickets/:id/assign   # Assign ticket
PUT  /admin/support-tickets/:id/resolve  # Resolve ticket

6.9 Admin Endpoints ​

# Users
GET  /admin/users                    # List all users
GET  /admin/users/:id                # Get user details
PUT  /admin/users/:id/ban            # Ban user
PUT  /admin/users/:id/unban          # Unban user

# Advertisers
GET  /admin/advertisers              # List all advertisers
GET  /admin/advertisers/:id          # Get advertiser details
PUT  /admin/advertisers/:id/approve  # Approve advertiser
PUT  /admin/advertisers/:id/suspend  # Suspend advertiser

# Analytics
GET  /admin/analytics/overview       # Platform overview
GET  /admin/analytics/users          # User metrics
GET  /admin/analytics/revenue        # Revenue metrics
GET  /admin/analytics/geographic     # Geographic distribution

# System
GET  /admin/system/health            # System health check
GET  /admin/system/activity-logs     # Activity logs

6.10 Webhook Endpoints ​

# Internal (Pub/Sub)
POST /webhooks/screenshot-verified   # Screenshot verification result
POST /webhooks/video-personalized    # Video personalization complete
POST /webhooks/payment-processed     # Payment processing result

# External
POST /api/deposits/webhook/stripe    # Stripe payment events
POST /api/stripe-billing/webhook     # Stripe billing events

7. Service Architecture ​

7.1 System Overview ​

┌──────────────────────────────────────────────────────────────────────────────────┐
│                           ENEZA ARCHITECTURE                                     │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  CLIENTS                                                                         │
│  ═══════                                                                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐            │
│  │   Mobile    │  │   Admin     │  │   Website   │  │   CEO       │            │
│  │   App       │  │  Dashboard  │  │  (Grow)     │  │  Dashboard  │            │
│  │  (Flutter)  │  │   (React)   │  │  (Next.js)  │  │  (Ext.)     │            │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘            │
│         │                │                │                │                    │
│         └────────────────┴────────┬───────┴────────────────┘                    │
│                                   │                                              │
│  API LAYER                        ▼                                              │
│  ═════════               ┌─────────────────┐                                    │
│                          │   ENEZA API     │                                    │
│                          │  (Node.js/TS)   │                                    │
│                          │   Cloud Run     │                                    │
│                          └────────┬────────┘                                    │
│                                   │                                              │
│  MESSAGE BROKER                   ▼                                              │
│  ══════════════          ┌─────────────────┐                                    │
│                          │  Google Pub/Sub │                                    │
│                          │  (Message Bus)  │                                    │
│                          └────────┬────────┘                                    │
│                                   │                                              │
│         ┌─────────────────────────┼─────────────────────────┐                   │
│         │                         │                         │                   │
│         ▼                         ▼                         ▼                   │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐             │
│  │  NOTIFICATION   │    │   SCREENSHOT    │    │     MEDIA       │             │
│  │    WORKER       │    │   VERIFIER      │    │    MANAGER      │             │
│  │   (Node.js)     │    │   (Python/ML)   │    │    (Python)     │             │
│  │   Cloud Run     │    │   Cloud Run     │    │   Cloud Run     │             │
│  └─────────────────┘    └─────────────────┘    └─────────────────┘             │
│         │                         │                         │                   │
│         │                         │                         │                   │
│  STORAGE LAYER                    │                         │                   │
│  ═════════════                    │                         │                   │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐             │
│  │    Neon        │    │  Cloudflare R2  │    │    Gemini AI    │             │
│  │   PostgreSQL    │    │  (Object Store) │    │   (Vision API)  │             │
│  └─────────────────┘    └─────────────────┘    └─────────────────┘             │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

7.2 Microservices ​

eneza-api (Main Backend) ​

  • Runtime: Node.js/TypeScript
  • Framework: Express.js
  • Database: Prisma ORM → Neon PostgreSQL
  • Hosting: Google Cloud Run
  • Responsibilities:
    • REST API for all clients
    • Authentication (JWT)
    • Business logic
    • Message publishing (Pub/Sub)
    • Stripe integration

eneza-notification-worker ​

  • Runtime: Node.js/TypeScript
  • Hosting: Google Cloud Run (always-on)
  • Responsibilities:
    • Process Pub/Sub messages
    • Send push notifications (FCM)
    • Send SMS notifications
    • Send email notifications
    • Process transaction callbacks

eneza-screenshot-verifier ​

  • Runtime: Python 3.11
  • AI: Google Gemini Vision API
  • Hosting: Google Cloud Run
  • Responsibilities:
    • Multi-check verification pipeline
    • QR code detection and validation
    • Image-to-image matching
    • View count extraction
    • Fraud detection scoring

eneza-media-manager ​

  • Runtime: Python 3.11
  • Libraries: FFmpeg, OpenCV, QRCode
  • Hosting: Google Cloud Run
  • Storage: Cloudflare R2
  • Responsibilities:
    • Video personalization
    • QR code watermarking
    • Video transcoding (multiple resolutions)
    • Thumbnail generation

7.3 Ad Distribution Flow ​

┌──────────────────────────────────────────────────────────────────────────────────┐
│                      AD DISTRIBUTION FLOW                                        │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  1. ADVERTISER CREATES CAMPAIGN                                                  │
│  ═════════════════════════════════                                               │
│                                                                                  │
│  ┌─────────────┐                    ┌─────────────┐                             │
│  │ Advertiser  │───Upload Video────▶│   R2/S3     │                             │
│  │  Dashboard  │                    │   Storage   │                             │
│  └─────────────┘                    └─────────────┘                             │
│         │                                  │                                     │
│         │ Create Campaign                  │ Transcode to variants              │
│         ▼                                  ▼                                     │
│  ┌─────────────┐                    ┌─────────────┐                             │
│  │  Eneza API  │───────────────────▶│   Media     │                             │
│  │             │    Pub/Sub         │   Manager   │                             │
│  └─────────────┘                    └─────────────┘                             │
│                                            │                                     │
│  2. USER SUBSCRIBES                        │                                     │
│  ═══════════════════                       ▼                                     │
│                                     ┌─────────────┐                             │
│  ┌─────────────┐                    │  Variants:  │                             │
│  │  Mobile     │───Subscribe───────▶│  - 1080p    │                             │
│  │    App      │                    │  - 720p     │                             │
│  └─────────────┘                    │  - 480p     │                             │
│         │                           └─────────────┘                             │
│         │ Generate unique code                                                   │
│         ▼                                                                        │
│  ┌─────────────┐                    ┌─────────────┐                             │
│  │  Eneza API  │───Personalize─────▶│   Media     │                             │
│  │             │    Pub/Sub         │   Manager   │                             │
│  └─────────────┘                    └─────────────┘                             │
│                                            │                                     │
│                                            │ Add QR watermark                    │
│                                            │ with subscription code              │
│                                            ▼                                     │
│  3. USER DOWNLOADS PERSONALIZED VIDEO      │                                     │
│  ════════════════════════════════════      │                                     │
│                                     ┌─────────────┐                             │
│  ┌─────────────┐                    │ Personalized│                             │
│  │  Mobile     │◀──Download────────│   Video     │                             │
│  │    App      │                    │ (with QR)   │                             │
│  └─────────────┘                    └─────────────┘                             │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

7.4 Screenshot Verification Flow ​

┌──────────────────────────────────────────────────────────────────────────────────┐
│                   SCREENSHOT VERIFICATION FLOW                                   │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐               │
│  │   Mobile    │──POST──▶│  Eneza API  │──Pub/Sub▶│ Screenshot  │               │
│  │     App     │         │             │         │  Verifier   │               │
│  └─────────────┘         └─────────────┘         └─────────────┘               │
│                                                         │                        │
│                                                         ▼                        │
│                                            ┌────────────────────────┐           │
│                                            │  VERIFICATION PIPELINE │           │
│                                            ├────────────────────────┤           │
│                                            │                        │           │
│                                            │  1. FORMAT CHECK       │           │
│                                            │     • Image valid?     │           │
│                                            │     • Resolution OK?   │           │
│                                            │     • Not corrupted?   │           │
│                                            │                        │           │
│                                            │  2. QR CODE CHECK      │           │
│                                            │     • QR detected?     │           │
│                                            │     • Code matches     │           │
│                                            │       subscription?    │           │
│                                            │     • Not tampered?    │           │
│                                            │                        │           │
│                                            │  3. IMAGE MATCHING     │           │
│                                            │     • Ad visible?      │           │
│                                            │     • Content matches  │           │
│                                            │       original ad?     │           │
│                                            │                        │           │
│                                            │  4. VIEW COUNTER       │           │
│                                            │     (Gemini Vision)    │           │
│                                            │     • Extract view #   │           │
│                                            │     • Validate range   │           │
│                                            │     • Check for fraud  │           │
│                                            │                        │           │
│                                            │  5. TIMESTAMP CHECK    │           │
│                                            │     • 20-24 hours      │           │
│                                            │       elapsed?         │           │
│                                            │                        │           │
│                                            │  6. DUPLICATE CHECK    │           │
│                                            │     • Not submitted    │           │
│                                            │       before?          │           │
│                                            │                        │           │
│                                            └────────────┬───────────┘           │
│                                                         │                        │
│                                                         ▼                        │
│                                            ┌────────────────────────┐           │
│                                            │  CALCULATE FINAL SCORE │           │
│                                            │                        │           │
│                                            │  Score = Weighted avg  │           │
│                                            │  of all check scores   │           │
│                                            │                        │           │
│                                            │  • > 0.8 = VERIFIED    │           │
│                                            │  • 0.5-0.8 = REVIEW    │           │
│                                            │  • < 0.5 = REJECTED    │           │
│                                            │  • Breaking check fail │           │
│                                            │    = FRAUD/REJECTED    │           │
│                                            └────────────┬───────────┘           │
│                                                         │                        │
│                                                         ▼                        │
│  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐               │
│  │   Mobile    │◀─Notify─│ Notification│◀─Pub/Sub│  Eneza API  │               │
│  │     App     │         │   Worker    │         │ (DB Update) │               │
│  └─────────────┘         └─────────────┘         └─────────────┘               │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

7.5 Payment Flow ​

┌──────────────────────────────────────────────────────────────────────────────────┐
│                         PAYMENT FLOWS                                            │
├──────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ADVERTISER DEPOSIT (Credit Card → USD Credits)                                  │
│  ═════════════════════════════════════════════                                   │
│                                                                                  │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐   │
│  │ Advertiser  │────▶│  Eneza API  │────▶│   Stripe    │────▶│  Webhook    │   │
│  │  Dashboard  │     │ Create      │     │  Checkout   │     │  Handler    │   │
│  └─────────────┘     │ Session     │     └─────────────┘     └─────────────┘   │
│                      └─────────────┘            │                   │           │
│                                                 │                   │           │
│                            ┌────────────────────┘                   │           │
│                            │ Payment Success                        │           │
│                            ▼                                        ▼           │
│                      ┌─────────────┐                         ┌─────────────┐   │
│                      │  Advertiser │ ◀───Credits Added───── │  Eneza API  │   │
│                      │   Account   │                         │ Process     │   │
│                      └─────────────┘                         └─────────────┘   │
│                                                                                  │
│  USER WITHDRAWAL (USD Balance → Mobile Money)                                    │
│  ═══════════════════════════════════════════                                    │
│                                                                                  │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐   │
│  │   Mobile    │────▶│  Eneza API  │────▶│  Payment    │────▶│  Mobile     │   │
│  │     App     │     │ Withdrawal  │     │  Processor  │     │  Money      │   │
│  │ Request     │     │  Request    │     │  (MTN/etc)  │     │  Account    │   │
│  └─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘   │
│                             │                   │                               │
│                             │                   │                               │
│                             ▼                   ▼                               │
│                      ┌─────────────┐     ┌─────────────┐                        │
│                      │  Balance    │     │  Callback   │                        │
│                      │  Deducted   │◀────│  Received   │                        │
│                      │  (Pending)  │     │ (Success)   │                        │
│                      └─────────────┘     └─────────────┘                        │
│                                                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

8. Authentication & Authorization ​

8.1 User Authentication (Phone + OTP) ​

typescript
// Flow:
// 1. User enters phone number
// 2. System sends OTP via SMS (Twilio/Africa's Talking)
// 3. User enters OTP
// 4. System issues JWT + refresh token

// JWT Payload
interface UserJwtPayload {
  userId: string;
  phoneNumber: string;
  countryId: string;
  tier: string;
  isVerified: boolean;
  isPremium: boolean;
}

// Token Expiry
const ACCESS_TOKEN_EXPIRY = '15m';   // 15 minutes
const REFRESH_TOKEN_EXPIRY = '30d';  // 30 days

8.2 Advertiser Authentication (Email + Password) ​

typescript
// Flow:
// 1. Advertiser registers with email + password
// 2. System sends verification email
// 3. Advertiser verifies email
// 4. Advertiser logs in, receives JWT

// JWT Payload
interface AdvertiserJwtPayload {
  advertiserId: string;
  email: string;
  companyName: string | null;
  isVerified: boolean;
  status: AdvertiserStatus;
}

8.3 Admin Authentication ​

typescript
// Separate admin auth with role-based access
interface AdminJwtPayload {
  adminId: string;
  email: string;
  role: 'SUPER_ADMIN' | 'ADMIN' | 'MODERATOR' | 'SUPPORT';
  permissions: string[];
}

8.4 Authorization Middleware ​

typescript
// Role-based middleware
app.use('/admin', verifyToken, isAdmin);
app.use('/advertisers', verifyToken, isAdvertiser);
app.use('/users', verifyToken, isUser);

// Permission checks
const canApproveAds = hasPermission('ads:approve');
const canBanUsers = hasPermission('users:ban');
const canProcessPayments = hasPermission('payments:process');

9. Billing System ​

9.1 Advertiser Billing ​

Credit System ​

  • Advertisers deposit USD via Stripe
  • Credits are added to their account (1 credit = $1 USD)
  • Volume discounts available:
    • $100+: 5% bonus
    • $500+: 10% bonus
    • $1,000+: 15% bonus
    • $5,000+: 20% bonus

Campaign Budgeting ​

  • Each campaign has a USD budget
  • Budget is deducted as views are verified
  • Campaign pauses when budget exhausted
  • Cost per view varies by country tier

Stripe Integration ​

typescript
// Checkout session creation
const session = await stripe.checkout.sessions.create({
  customer: advertiser.stripeCustomerId,
  mode: 'payment',
  line_items: [{
    price_data: {
      currency: 'usd',
      product_data: { name: 'Eneza Ad Credits' },
      unit_amount: amountCents,
    },
    quantity: 1,
  }],
  success_url: `${baseUrl}/deposits/success`,
  cancel_url: `${baseUrl}/deposits/cancel`,
  metadata: {
    depositId,
    advertiserId,
    bonusPercent,
  },
});

9.2 User Payouts ​

Earnings Calculation ​

typescript
// Per-view earnings formula
const earnings = baseRate 
  * viewCount 
  * tierMultiplier 
  * streakMultiplier;

// Example:
// $0.002/view × 50 views × 1.2 (Gold tier) × 1.1 (7-day streak)
// = $0.132 USD

Withdrawal Process ​

  1. User requests withdrawal (min $0.50 - $2 depending on country)
  2. System converts USD to local currency at current rate
  3. Transaction created with PENDING status
  4. Admin approves (or auto-approve for trusted users)
  5. Payment processor API called
  6. Callback received, transaction marked COMPLETED

Payment Processors by Country ​

CountryProcessorCurrency
KenyaM-PesaKES
NigeriaFlutterwaveNGN
South AfricaPayFastZAR
GhanaMTN MoMoGHS
UgandaAirtel MoneyUGX
TanzaniaVodacom M-PesaTZS
EswatiniMTN MoMoSZL

9.3 Invoice System ​

typescript
// Invoice generation
const invoice = await invoiceService.create({
  advertiserId,
  depositId,
  items: [{
    description: 'Ad Credits Deposit',
    quantity: 1,
    unitPrice: amount,
  }],
  taxRate: 0, // No VAT currently
});

// PDF generation via Cloudflare Pages Function
const pdfUrl = await generateInvoicePdf(invoice);

10. Verification System ​

10.1 Verification Pipeline ​

The screenshot verification system uses a multi-check pipeline where each check contributes to an overall verification score.

python
# Pipeline configuration
VERIFICATION_CHECKS = [
    {
        "name": "format_check",
        "weight": 0.1,
        "is_breaking": True,  # Fails immediately if this fails
    },
    {
        "name": "qr_code_check", 
        "weight": 0.9,
        "is_breaking": True,
    },
    {
        "name": "image_matching",
        "weight": 0.7,
        "is_breaking": False,  # Contributes to aggregate score
    },
    {
        "name": "view_counter",
        "weight": 0.6,
        "is_breaking": False,
    },
    {
        "name": "timestamp_check",
        "weight": 0.8,
        "is_breaking": True,
    },
    {
        "name": "duplicate_check",
        "weight": 1.0,
        "is_breaking": True,
    },
]

10.2 Individual Checks ​

Format Check ​

  • Image is valid (PNG, JPEG)
  • Resolution within acceptable range
  • File not corrupted

QR Code Check ​

  • QR code detected in image
  • QR code decodes successfully
  • Decoded value matches subscription code
  • QR position consistent with watermark placement

Image Matching ​

  • Ad content visible in screenshot
  • Color histogram comparison with original
  • Feature matching (ORB/SIFT)
  • Structural similarity (SSIM)

View Counter (Gemini Vision AI) ​

python
# Gemini prompt for view extraction
prompt = """
Analyze this screenshot of a WhatsApp status view.
Extract the view count number shown in the screenshot.
Look for a number indicating how many people have viewed the status.

Return JSON:
{
  "view_count": <number or null>,
  "confidence": <0.0 to 1.0>,
  "is_whatsapp_status": <true/false>,
  "additional_info": "<observations>"
}
"""

Timestamp Check ​

  • Subscription was 20-24 hours ago (WhatsApp 24h limit)
  • Screenshot not submitted too early
  • Not expired (past 24h window)

Duplicate Check ​

  • Image hash not seen before
  • Perceptual hash (pHash) comparison
  • Same user hasn't submitted similar image

10.3 Fraud Detection ​

python
# Fraud indicators
FRAUD_SIGNALS = [
    "qr_code_missing",           # No QR watermark found
    "qr_code_mismatch",          # QR doesn't match subscription
    "view_count_suspiciously_high",  # > 500 views
    "view_count_mismatch",       # Extracted ≠ submitted
    "image_too_similar",         # Near-duplicate of previous
    "timestamp_invalid",         # Submitted too early/late
    "device_fingerprint_mismatch",   # Different device
    "rapid_submissions",         # Too many submissions quickly
]

# Auto-ban threshold
CRITICAL_FAILURE_THRESHOLD = 3  # 3 critical failures = ban

10.4 Manual Review Queue ​

Screenshots with borderline scores (0.5-0.8) go to manual review:

typescript
// Admin review actions
await screenshotService.manualApprove(screenshotId, {
  adminId,
  reason: 'QR visible, view count verified manually',
  adjustedViews: 45,  // Can adjust if needed
});

await screenshotService.manualReject(screenshotId, {
  adminId,
  reason: 'Screenshot appears edited',
  markAsFraud: true,  // Increment user's fraud count
});

11. Notifications ​

11.1 Notification Types ​

Push Notifications (FCM) ​

  • New ad available in your country
  • Subscription expiring soon
  • Screenshot verified (with earnings)
  • Screenshot rejected (with reason)
  • Withdrawal processed
  • Achievement unlocked
  • Streak at risk

Email Notifications ​

  • Advertiser: Welcome email
  • Advertiser: Email verification
  • Advertiser: Deposit confirmation
  • Advertiser: Campaign performance weekly digest

Admin Notifications ​

  • New user registration
  • New advertiser signup
  • Large deposit received
  • Withdrawal request pending
  • Fraud alert triggered
  • Support ticket created

11.2 Notification Worker ​

typescript
// Pub/Sub topic handlers
const handlers = {
  'screenshot-verified': async (data) => {
    // Send push to user
    await fcmService.send(data.userId, {
      title: 'Screenshot Verified! 🎉',
      body: `You earned $${data.earnings} from ${data.viewCount} views!`,
    });
  },
  
  'withdrawal-completed': async (data) => {
    // Send push + SMS
    await fcmService.send(data.userId, {...});
    await smsService.send(data.phoneNumber, `Your withdrawal of ${data.amount} has been sent!`);
  },
  
  'streak-at-risk': async (data) => {
    // Reminder to maintain streak
    await fcmService.send(data.userId, {
      title: 'Your streak is at risk! 🔥',
      body: 'Submit a screenshot today to keep your streak alive!',
    });
  },
};

11.3 Transactional Notifications ​

typescript
// HTTP-based notifications for critical events
await sendTransactionalNotification(userId, {
  type: 'EARNING',
  title: 'You earned money!',
  body: `$${amount} added to your wallet`,
  data: {
    screenshotId,
    subscriptionId,
    amount,
  },
});

12. Analytics & Reporting ​

12.1 Platform Metrics ​

typescript
// Dashboard overview
interface DashboardMetrics {
  users: {
    total: number;
    active30d: number;
    newToday: number;
    newThisWeek: number;
  };
  advertisers: {
    total: number;
    active: number;
    newThisMonth: number;
  };
  campaigns: {
    total: number;
    active: number;
    totalBudgetUsd: number;
  };
  revenue: {
    totalDepositsUsd: number;
    totalPayoutsUsd: number;
    platformFeesUsd: number;
  };
  screenshots: {
    totalSubmitted: number;
    verifiedToday: number;
    rejectedToday: number;
    pendingReview: number;
  };
}

12.2 Advertiser Analytics ​

typescript
// Campaign performance
interface CampaignAnalytics {
  views: {
    total: number;
    verified: number;
    pending: number;
    rejected: number;
  };
  subscriptions: {
    total: number;
    active: number;
    completed: number;
    expired: number;
  };
  spend: {
    totalUsd: number;
    costPerView: number;
    remainingBudget: number;
  };
  geographic: {
    [countryCode: string]: {
      views: number;
      subscriptions: number;
    };
  };
  timeline: {
    date: string;
    views: number;
    subscriptions: number;
    spend: number;
  }[];
}

12.3 User Analytics ​

typescript
// User performance
interface UserAnalytics {
  earnings: {
    totalUsd: number;
    thisMonthUsd: number;
    pendingUsd: number;
    withdrawnUsd: number;
  };
  screenshots: {
    total: number;
    approved: number;
    rejected: number;
    approvalRate: number;
  };
  referrals: {
    total: number;
    active: number;
    bonusEarnedUsd: number;
  };
  streak: {
    current: number;
    longest: number;
    multiplier: number;
  };
  tier: {
    name: string;
    multiplier: number;
    progress: number;  // To next tier
  };
}

12.4 CEO Dashboard Integration ​

The Eneza API exposes metrics for the CEO dashboard:

typescript
// GET /api/dashboard/metrics
interface EnezaDashboardMetrics {
  totalUsers: number;
  activeUsersLast30Days: number;
  totalAdvertisers: number;
  activeCampaigns: number;
  totalRevenueUsd: number;
  totalPayoutsUsd: number;
  platformProfitUsd: number;
  screenshotsVerifiedToday: number;
  fraudRate: number;  // % of rejected screenshots
}

13. Technical Stack ​

13.1 Backend ​

ComponentTechnology
RuntimeNode.js 20 (TypeScript)
FrameworkExpress.js 4.x
ORMPrisma 5.x
DatabasePostgreSQL 15 (Neon Serverless)
CachingRedis (Upstash)
Message QueueGoogle Cloud Pub/Sub
Object StorageCloudflare R2
PaymentsStripe
Push NotificationsFirebase Cloud Messaging (FCM)
SMSTwilio / Africa's Talking
EmailResend
AI/MLGoogle Gemini Vision API

13.2 Frontend (Admin Dashboard) ​

ComponentTechnology
FrameworkReact 18
Build ToolVite
StylingTailwind CSS
State ManagementTanStack Query (React Query)
ChartsRecharts
HostingCloudflare Pages

13.3 Infrastructure ​

ComponentTechnology
API HostingGoogle Cloud Run
CDNCloudflare
DNSCloudflare
CI/CDGoogle Cloud Build
MonitoringGoogle Cloud Logging
Error TrackingSentry

13.4 Mobile App ​

ComponentTechnology
FrameworkFlutter
State ManagementRiverpod
API ClientDio
DistributionGoogle Play / Direct APK

14. Roadmap ​

Q2 2026 (Current) ​

  • [x] Core platform live with 12k+ users
  • [x] AI-powered screenshot verification
  • [x] Mobile money payouts (Kenya, Nigeria, South Africa)
  • [x] Stripe billing for advertisers
  • [x] Admin dashboard
  • [x] Referral system with tiers
  • [ ] IN PROGRESS: Multi-language support
  • [ ] IN PROGRESS: WhatsApp Business API integration

Q3 2026 ​

  • [ ] Self-serve advertiser onboarding
  • [ ] Campaign A/B testing
  • [ ] Advanced fraud detection (ML model)
  • [ ] iOS app launch
  • [ ] WhatsApp status direct posting (via WA Business API)
  • [ ] Expand to 10 more African countries

Q4 2026 ​

  • [ ] Agency dashboard (manage multiple advertisers)
  • [ ] Influencer tiers (premium posters with larger audiences)
  • [ ] Real-time bidding for ad slots
  • [ ] Integration with WhatsApp Channels
  • [ ] API for third-party integrations

2027 ​

  • [ ] Instagram/TikTok status expansion
  • [ ] Brand ambassador program
  • [ ] Enterprise features
  • [ ] White-label platform licensing

15. Gaps & Needed Improvements ​

15.1 Critical Gaps ​

Video Personalization Reliability ​

  • Issue: Media Manager occasionally fails to generate personalized videos
  • Impact: Users can't download videos, blocking the flow
  • Needed: Better error handling, retry logic, fallback to non-personalized video

Payment Processor Integration ​

  • Issue: Only MTN MoMo fully integrated; other processors have limited support
  • Impact: Users in some countries can't withdraw
  • Needed: Complete integration for Airtel Money, Orange Money, Flutterwave

Screenshot Verification Accuracy ​

  • Issue: ~15% of screenshots go to manual review
  • Impact: Admin overhead, delayed payouts
  • Needed: Improve ML model, better training data, reduce false positives

15.2 Feature Gaps ​

Missing Advertiser Features ​

  • No campaign scheduling (start/end dates)
  • No audience targeting beyond country
  • No ad preview before going live
  • No real-time campaign performance alerts
  • No bulk campaign management

Missing User Features ​

  • No in-app help/tutorials
  • No earnings history export
  • No push notification preferences
  • No account deletion (GDPR compliance)
  • No multiple payment methods per user

Missing Admin Features ​

  • No bulk screenshot review
  • No automated fraud banning (currently manual)
  • No financial reconciliation reports
  • No advertiser credit management UI
  • No system health alerts

15.3 Technical Debt ​

Database ​

  • Some queries not optimized (full table scans)
  • Missing indexes on frequently queried columns
  • Denormalized data sometimes out of sync (AdStats)
  • Need to implement database connection pooling

API ​

  • Inconsistent error response formats
  • Missing rate limiting on some endpoints
  • No API versioning (/v1/, /v2/)
  • OpenAPI/Swagger documentation incomplete

Codebase ​

  • Some services have tight coupling
  • Test coverage < 50%
  • Environment configuration scattered
  • Logging inconsistent across services

15.4 Scalability Concerns ​

Database ​

  • Single Neon instance (no read replicas)
  • Screenshot images stored as URLs, not properly archived
  • Transaction history growing unbounded

Message Queue ​

  • No dead letter queue for failed messages
  • No message deduplication
  • Retry logic not consistent across handlers

Video Processing ​

  • Single media-manager instance
  • No queue depth monitoring
  • Large videos timeout

15.5 Security Improvements Needed ​

  • [ ] Implement API key rotation for advertisers
  • [ ] Add 2FA for admin accounts
  • [ ] Encrypt sensitive fields in database (account numbers)
  • [ ] Implement request signing for webhooks
  • [ ] Add IP whitelisting for admin endpoints
  • [ ] Regular security audits

15.6 Compliance Gaps ​

  • [ ] GDPR: No data export/deletion workflows
  • [ ] POPIA (South Africa): Privacy policy needs update
  • [ ] Financial regulations: KYC not implemented for high-value users
  • [ ] Tax compliance: No automatic tax calculation for payouts

15.7 Priority Fixes (Next Sprint) ​

  1. Fix video personalization failures - Critical blocker
  2. Add missing payment processors - Revenue blocker
  3. Improve verification accuracy - Reduces manual work
  4. Implement API rate limiting - Security
  5. Add database indexes - Performance

Appendix A: Environment Variables ​

bash
# Database
DATABASE_URL=postgresql://...@neon.tech/eneza

# Authentication
JWT_SECRET=...
JWT_REFRESH_SECRET=...
ADMIN_JWT_SECRET=...

# Stripe
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PUBLISHABLE_KEY=pk_live_...

# Google Cloud
GCP_PROJECT_ID=eneza-...
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json

# Gemini AI
GEMINI_API_KEY=AIza...

# Cloudflare R2
R2_ACCOUNT_ID=...
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...
R2_BUCKET_NAME=eneza-media

# Firebase (FCM)
FIREBASE_PROJECT_ID=...
FIREBASE_PRIVATE_KEY=...
FIREBASE_CLIENT_EMAIL=...

# SMS
TWILIO_ACCOUNT_SID=...
TWILIO_AUTH_TOKEN=...
TWILIO_PHONE_NUMBER=+1...

# Email
RESEND_API_KEY=re_...

# Admin
ADMIN_NOTIFICATION_EMAIL=admin@eneza.app
INITIAL_ADMIN_EMAIL=...
INITIAL_ADMIN_PASSWORD=...

Appendix B: API Error Codes ​

CodeDescription
AUTH_001Invalid or expired token
AUTH_002OTP expired
AUTH_003Invalid OTP
AUTH_004Email not verified
USER_001User not found
USER_002User blocked
USER_003Invalid phone number
AD_001Ad not found
AD_002Ad not active
AD_003Ad budget exhausted
SUB_001Already subscribed
SUB_002Subscription expired
SUB_003Max subscriptions reached
SCREEN_001Invalid screenshot format
SCREEN_002Verification failed
SCREEN_003Duplicate screenshot
PAY_001Insufficient balance
PAY_002Below minimum withdrawal
PAY_003Payment processor error

Document generated from codebase analysis. Last updated: March 2026.

One chat. Everything done.