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
- Vision & Problem Statement
- Solution Overview
- Core Features
- User Journeys
- Data Models
- API Reference
- Service Architecture
- Authentication & Authorization
- Billing System
- Verification System
- Notifications
- Analytics & Reporting
- Technical Stack
- Roadmap
- Gaps & Needed Improvements
1. Vision & Problem Statement
The Problem
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)
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
| Feature | Traditional Ads | Eneza |
|---|---|---|
| View Verification | Self-reported | AI-verified screenshots |
| Audience Trust | Low (strangers) | High (friends/contacts) |
| Fraud Detection | Basic | ML-powered multi-check pipeline |
| Personalization | Generic | QR-coded watermarked videos |
| Payout | N/A | Real-time mobile money |
| Geographic Focus | Global | Africa-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
| Tier | Referrals | Earnings Multiplier |
|---|---|---|
| Bronze | 0-4 | 1.0x |
| Silver | 5-14 | 1.1x (+10%) |
| Gold | 15-29 | 1.2x (+20%) |
| Platinum | 30-49 | 1.3x (+30%) |
| Diamond | 50+ | 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
- Registration: Email + password + company details
- Email Verification: Required before depositing
- Stripe Checkout: Secure payment with Stripe
- Campaign Creation: Upload video, set budget, choose countries
- Campaign Activation: Automatic when budget is funded
- Monitoring: Real-time dashboard with live stats
- 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
- Registration: Phone number + OTP verification
- Country Selection: Determines available campaigns and payout methods
- Profile Setup: Date of birth validation
- Ad Browsing: Filter by category, earnings potential
- Subscription: Get unique subscription code
- Video Download: Personalized with QR watermark
- WhatsApp Posting: User posts to their status
- Wait Period: 20-24 hours minimum
- Screenshot Upload: Must show view count
- Verification: AI pipeline processes screenshot
- Payout: View count × rate = earnings
- 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)
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
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)
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)
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)
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
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)
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
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
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
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
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
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
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
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
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
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
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)
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 JWTAdvertiser 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 tokenAdmin Auth
POST /admin/auth/login # Admin login
POST /admin/auth/logout # Admin logout
GET /admin/auth/me # Get current admin6.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 ad6.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 status6.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 reject6.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 withdrawal6.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 handler6.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 data6.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 ticket6.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 logs6.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 events7. 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)
// 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 days8.2 Advertiser Authentication (Email + Password)
// 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
// 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
// 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
// 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
// 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 USDWithdrawal Process
- User requests withdrawal (min $0.50 - $2 depending on country)
- System converts USD to local currency at current rate
- Transaction created with PENDING status
- Admin approves (or auto-approve for trusted users)
- Payment processor API called
- Callback received, transaction marked COMPLETED
Payment Processors by Country
| Country | Processor | Currency |
|---|---|---|
| Kenya | M-Pesa | KES |
| Nigeria | Flutterwave | NGN |
| South Africa | PayFast | ZAR |
| Ghana | MTN MoMo | GHS |
| Uganda | Airtel Money | UGX |
| Tanzania | Vodacom M-Pesa | TZS |
| Eswatini | MTN MoMo | SZL |
9.3 Invoice System
// 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.
# 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)
# 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
# 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 = ban10.4 Manual Review Queue
Screenshots with borderline scores (0.5-0.8) go to manual review:
// 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
// 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
// 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
// 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
// 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
// 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:
// 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
| Component | Technology |
|---|---|
| Runtime | Node.js 20 (TypeScript) |
| Framework | Express.js 4.x |
| ORM | Prisma 5.x |
| Database | PostgreSQL 15 (Neon Serverless) |
| Caching | Redis (Upstash) |
| Message Queue | Google Cloud Pub/Sub |
| Object Storage | Cloudflare R2 |
| Payments | Stripe |
| Push Notifications | Firebase Cloud Messaging (FCM) |
| SMS | Twilio / Africa's Talking |
| Resend | |
| AI/ML | Google Gemini Vision API |
13.2 Frontend (Admin Dashboard)
| Component | Technology |
|---|---|
| Framework | React 18 |
| Build Tool | Vite |
| Styling | Tailwind CSS |
| State Management | TanStack Query (React Query) |
| Charts | Recharts |
| Hosting | Cloudflare Pages |
13.3 Infrastructure
| Component | Technology |
|---|---|
| API Hosting | Google Cloud Run |
| CDN | Cloudflare |
| DNS | Cloudflare |
| CI/CD | Google Cloud Build |
| Monitoring | Google Cloud Logging |
| Error Tracking | Sentry |
13.4 Mobile App
| Component | Technology |
|---|---|
| Framework | Flutter |
| State Management | Riverpod |
| API Client | Dio |
| Distribution | Google 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)
- Fix video personalization failures - Critical blocker
- Add missing payment processors - Revenue blocker
- Improve verification accuracy - Reduces manual work
- Implement API rate limiting - Security
- Add database indexes - Performance
Appendix A: Environment Variables
# 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
| Code | Description |
|---|---|
| AUTH_001 | Invalid or expired token |
| AUTH_002 | OTP expired |
| AUTH_003 | Invalid OTP |
| AUTH_004 | Email not verified |
| USER_001 | User not found |
| USER_002 | User blocked |
| USER_003 | Invalid phone number |
| AD_001 | Ad not found |
| AD_002 | Ad not active |
| AD_003 | Ad budget exhausted |
| SUB_001 | Already subscribed |
| SUB_002 | Subscription expired |
| SUB_003 | Max subscriptions reached |
| SCREEN_001 | Invalid screenshot format |
| SCREEN_002 | Verification failed |
| SCREEN_003 | Duplicate screenshot |
| PAY_001 | Insufficient balance |
| PAY_002 | Below minimum withdrawal |
| PAY_003 | Payment processor error |
Document generated from codebase analysis. Last updated: March 2026.