Skip to content

YeboSafe PRD (Product Requirements Document)

Product Name: YeboSafe
Type: African Escrow & Payment Protection API Infrastructure
Status: Built & Deployed
API URL: https://yebosafe-api-1009635282906.europe-west1.run.app
Last Updated: 2026-03-19


1. Vision & Problem Statement

1.1 Vision

YeboSafe is a standalone escrow API infrastructure designed for African markets, enabling any platform, marketplace, or application to integrate secure payment protection without building complex escrow systems from scratch. It transforms the "completion code" escrow pattern (proven in YeboShops) into a reusable API that any business can plug into.

1.2 Problem Statement

African digital commerce faces critical trust and payment challenges:

  1. Platform Integration Burden: Every marketplace/app must build their own escrow system, duplicating effort and increasing risk of flawed implementations
  2. Cross-Platform Trust: Buyers transacting on multiple platforms have no unified payment protection layer
  3. Fraud Exposure: Small platforms lack the resources to implement robust escrow, leaving buyers and sellers vulnerable
  4. Developer Experience: No simple "plug and play" escrow API exists for African fintech developers
  5. Compliance Complexity: Each platform must handle payment regulations, audit trails, and dispute resolution independently

1.3 Solution

YeboSafe provides:

  • API-First Escrow Infrastructure: Simple REST API for creating, managing, and completing escrow transactions
  • Multi-Tenant Architecture: Each merchant (platform) gets isolated escrows, wallets, and API keys
  • Completion Code Pattern: Proven mechanism where buyers reveal a 6-digit code only after receiving goods/services
  • Webhook Integration: Real-time event notifications for status changes
  • Merchant Dashboard: Self-service portal for managing escrows, viewing balances, and generating API keys
  • Full Audit Trail: Every action logged for compliance and dispute resolution

1.4 Origin Story

YeboSafe was extracted from YeboShops' "Secure Payments" system. The original implementation in YeboShops proved the completion code escrow model works for African peer-to-peer commerce. YeboSafe generalizes this into a standalone API that any platform can integrate.


2. Technical Stack

2.1 Backend (yebosafe-api)

ComponentTechnology
RuntimeNode.js + TypeScript
FrameworkExpress.js 4.x
DatabasePostgreSQL (Neon serverless)
ORMPrisma 5.22
AuthJWT + API Key dual authentication
SecurityHelmet, CORS, bcryptjs
Validationexpress-validator
LoggingMorgan

2.2 Dashboard (yebosafe-dashboard)

ComponentTechnology
FrameworkReact 19 + TypeScript
Build ToolVite 7
StylingTailwind CSS 3.4
HTTP ClientAxios
RoutingReact Router 7
IconsLucide React

2.3 Infrastructure

ComponentService
API HostingGoogle Cloud Run (europe-west1)
DatabaseNeon Serverless PostgreSQL
Dashboard HostingCloudflare Pages (planned)
CI/CDCloud Build

3. Core Features (Implemented)

3.1 Escrow Management

Create Escrow Transaction

  • Merchant creates escrow with amount, currency, description, and optional payer info
  • System generates unique 6-digit completion code
  • Escrow starts in PENDING status
  • Webhook fired: escrow.created

Accept Escrow

  • Merchant confirms they will fulfill the transaction
  • Status moves to ACCEPTED
  • Webhook fired: escrow.accepted

Complete Escrow

  • Payer reveals completion code to merchant after receiving goods/services
  • Merchant enters code to release funds
  • Status moves to COMPLETED
  • Funds credited to merchant wallet
  • Webhook fired: escrow.completed

Refuse Escrow

  • Merchant can refuse before accepting
  • Requires reason
  • Status moves to REFUSED
  • Webhook fired: escrow.refused

Dispute Escrow

  • Either party can raise dispute before completion
  • Requires reason
  • Status moves to DISPUTED
  • Webhook fired: escrow.disputed

Cancel Escrow

  • Escrow can be cancelled before completion
  • Requires reason
  • Status moves to CANCELLED
  • Webhook fired: escrow.cancelled

3.2 Merchant Management

Registration

  • Email + password authentication
  • Automatic initial API key generation
  • Automatic wallet creation
  • JWT token issued for dashboard access

API Key Management

  • Generate multiple named API keys
  • Keys formatted as ys_live_{uuid}
  • Track last usage timestamp
  • Revoke keys without affecting others

Webhook Configuration

  • Set webhook URL for event notifications
  • HMAC-SHA256 signed payloads
  • Automatic retry (up to 3 attempts with exponential backoff)

3.3 Wallet System

Merchant Wallet

  • One wallet per merchant
  • Multi-currency support (default USD)
  • Balance updated automatically on escrow completion
  • Full transaction history

Withdrawal Requests

  • Merchants can request withdrawals
  • Creates DEBIT entry in transaction log
  • Actual payout handled externally (manual or future integration)

3.4 Dashboard Portal

Overview Dashboard

  • Wallet balance display
  • Escrow statistics (total, pending, completed, disputed)
  • Recent escrows list
  • Dispute alerts

Escrow Management

  • List all escrows with filters (status, search)
  • Detailed escrow view with timeline
  • Action buttons (accept, refuse, complete, dispute, cancel)
  • Completion code display with copy functionality

Wallet Management

  • Balance display
  • Transaction history
  • Withdrawal request form

Developer Tools

  • API key generation and revocation
  • Webhook URL configuration
  • Code examples and event documentation

3.5 Admin Features

Platform Administration

  • List all escrows across merchants
  • List all merchants with wallet info
  • Platform-wide statistics

4. User Journeys

4.1 Platform Integrator Journey

1. Discovery
   └─ Find YeboSafe API documentation
   └─ Understand escrow flow and integration options

2. Onboarding
   └─ Register merchant account via dashboard or API
   └─ Receive initial API key
   └─ Configure webhook URL

3. Integration
   └─ Implement POST /v1/escrow in platform backend
   └─ Handle webhook events for status updates
   └─ Display completion code to buyers
   └─ Implement code verification flow for sellers

4. Operations
   └─ Monitor escrows via dashboard
   └─ Handle disputes manually or programmatically
   └─ Track wallet balance and request withdrawals

5. Scale
   └─ Generate additional API keys for different services
   └─ Implement advanced flows (refunds, partial releases)

4.2 Buyer Journey (via Integrated Platform)

1. Transaction Initiation
   └─ Buyer agrees to purchase on integrated platform
   └─ Platform creates escrow via YeboSafe API
   └─ Buyer receives completion code (kept secret)

2. Fulfillment
   └─ Seller delivers goods/services
   └─ Buyer verifies delivery

3. Release
   └─ Buyer reveals completion code to seller
   └─ Seller enters code on platform or via API
   └─ Funds released to seller

4. Dispute (if needed)
   └─ Either party raises dispute before completion
   └─ Platform/YeboSafe admin mediates

4.3 Seller Journey (via Integrated Platform)

1. Accept Order
   └─ Platform notifies seller of new escrow
   └─ Seller accepts via platform interface
   └─ Escrow status moves to ACCEPTED

2. Fulfillment
   └─ Seller delivers goods/services
   └─ Obtains completion code from buyer

3. Receive Payment
   └─ Enter completion code via platform or API
   └─ Funds credited to merchant wallet
   └─ Withdraw to bank/mobile money (future)

4. Refuse (if needed)
   └─ Can refuse escrow before accepting
   └─ Must provide reason

4.4 Admin Journey

1. Monitoring
   └─ View platform-wide statistics
   └─ Monitor active escrows across merchants
   └─ Track total volume and completion rates

2. Merchant Management
   └─ View all registered merchants
   └─ Monitor merchant wallet balances
   └─ Review escrow activity per merchant

3. Dispute Resolution
   └─ Review disputed escrows
   └─ Access full audit trail
   └─ Manual resolution actions (future)

5. Data Models

5.1 Merchant Model

prisma
model Merchant {
  id         String   @id @default(cuid())
  name       String                        // Business name
  email      String   @unique              // Login email
  password   String                        // bcrypt hashed
  webhookUrl String?                       // Webhook endpoint
  isActive   Boolean  @default(true)       // Account status
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt
  
  // Relations
  apiKeys    ApiKey[]
  escrows    EscrowTransaction[] @relation("MerchantEscrows")
  wallet     MerchantWallet?
}

Fields:

FieldTypeDescription
idString (CUID)Primary key
nameStringBusiness/merchant name
emailStringUnique login email
passwordStringbcrypt-hashed password
webhookUrlString?URL for webhook notifications
isActiveBooleanAccount active status
createdAtDateTimeRegistration timestamp
updatedAtDateTimeLast update timestamp

5.2 ApiKey Model

prisma
model ApiKey {
  id         String    @id @default(cuid())
  key        String    @unique             // Full API key (ys_live_xxx)
  prefix     String                        // First 12 chars for display
  name       String?                       // User-given name
  isActive   Boolean   @default(true)      // Key active status
  merchantId String                        // Owner merchant
  merchant   Merchant  @relation(fields: [merchantId], references: [id])
  createdAt  DateTime  @default(now())
  lastUsedAt DateTime?                     // Last API call timestamp
}

Fields:

FieldTypeDescription
idString (CUID)Primary key
keyStringFull API key (ys_live_{uuid})
prefixStringFirst 12 characters for safe display
nameString?Optional friendly name
isActiveBooleanWhether key is active
merchantIdStringFK to Merchant
createdAtDateTimeCreation timestamp
lastUsedAtDateTime?Last usage timestamp

5.3 EscrowTransaction Model

prisma
model EscrowTransaction {
  id             String        @id @default(cuid())
  reference      String        @unique @default(cuid())  // Public reference
  merchantId     String                                   // Owner merchant
  merchant       Merchant      @relation("MerchantEscrows", fields: [merchantId], references: [id])
  
  // Payer Information
  payerName      String?                                  // Buyer name
  payerEmail     String?                                  // Buyer email
  payerPhone     String?                                  // Buyer phone
  
  // Transaction Details
  amount         Decimal       @db.Decimal(12, 2)        // Amount in currency
  currency       String        @default("USD")            // ISO currency code
  description    String?                                  // Transaction description
  metadata       Json?                                    // Custom metadata
  
  // Status & Flow
  status         EscrowStatus  @default(PENDING)          // Current status
  completionCode String        @unique                    // 6-digit code
  webhookUrl     String?                                  // Override webhook URL
  
  // Timestamps
  completedAt    DateTime?                                // When completed
  disputedAt     DateTime?                                // When disputed
  disputeReason  String?                                  // Dispute reason
  cancelledAt    DateTime?                                // When cancelled
  cancelReason   String?                                  // Cancellation reason
  refundedAt     DateTime?                                // When refunded
  createdAt      DateTime      @default(now())
  updatedAt      DateTime      @updatedAt
  
  // Relations
  logs           EscrowLog[]
}

enum EscrowStatus {
  PENDING      // Created, awaiting acceptance
  ACCEPTED     // Merchant accepted, in escrow
  COMPLETED    // Code entered, funds released
  REFUSED      // Merchant refused to accept
  DISPUTED     // Dispute raised
  CANCELLED    // Cancelled before completion
  REFUNDED     // Funds returned (future)
}

Fields:

FieldTypeDescription
idString (CUID)Primary key
referenceStringPublic-facing reference ID
merchantIdStringFK to owning Merchant
payerNameString?Buyer's name
payerEmailString?Buyer's email
payerPhoneString?Buyer's phone
amountDecimal(12,2)Transaction amount
currencyStringISO currency code (default: USD)
descriptionString?Transaction description
metadataJson?Custom key-value data
statusEscrowStatusCurrent lifecycle status
completionCodeStringUnique 6-digit code
webhookUrlString?Override merchant webhook
completedAtDateTime?Completion timestamp
disputedAtDateTime?Dispute timestamp
disputeReasonString?Reason for dispute
cancelledAtDateTime?Cancellation timestamp
cancelReasonString?Reason for cancellation
refundedAtDateTime?Refund timestamp
createdAtDateTimeCreation timestamp
updatedAtDateTimeLast update timestamp

5.4 EscrowLog Model

prisma
model EscrowLog {
  id        String            @id @default(cuid())
  escrowId  String                                    // Parent escrow
  escrow    EscrowTransaction @relation(fields: [escrowId], references: [id])
  action    String                                    // Action name
  actorId   String?                                   // Who performed action
  metadata  Json?                                     // Action details
  createdAt DateTime          @default(now())
}

Fields:

FieldTypeDescription
idString (CUID)Primary key
escrowIdStringFK to EscrowTransaction
actionStringAction type (created, accepted, completed, etc.)
actorIdString?ID of actor (merchant ID)
metadataJson?Additional action data
createdAtDateTimeAction timestamp

5.5 MerchantWallet Model

prisma
model MerchantWallet {
  id           String        @id @default(cuid())
  merchantId   String        @unique                 // One wallet per merchant
  merchant     Merchant      @relation(fields: [merchantId], references: [id])
  balance      Decimal       @default(0) @db.Decimal(12, 2)
  currency     String        @default("USD")
  createdAt    DateTime      @default(now())
  updatedAt    DateTime      @updatedAt
  
  // Relations
  transactions WalletEntry[]
}

Fields:

FieldTypeDescription
idString (CUID)Primary key
merchantIdStringFK to Merchant (unique)
balanceDecimal(12,2)Current balance
currencyStringWallet currency
createdAtDateTimeCreation timestamp
updatedAtDateTimeLast update timestamp

5.6 WalletEntry Model

prisma
model WalletEntry {
  id          String         @id @default(cuid())
  walletId    String                                 // Parent wallet
  wallet      MerchantWallet @relation(fields: [walletId], references: [id])
  type        EntryType                              // CREDIT or DEBIT
  amount      Decimal        @db.Decimal(12, 2)     // Transaction amount
  description String?                                // Description
  reference   String?                                // External reference
  createdAt   DateTime       @default(now())
}

enum EntryType {
  CREDIT     // Money added to wallet
  DEBIT      // Money removed from wallet
}

Fields:

FieldTypeDescription
idString (CUID)Primary key
walletIdStringFK to MerchantWallet
typeEntryTypeCREDIT or DEBIT
amountDecimal(12,2)Transaction amount
descriptionString?Human-readable description
referenceString?External reference (escrow ID, etc.)
createdAtDateTimeEntry timestamp

5.7 WebhookEvent Model

prisma
model WebhookEvent {
  id         String   @id @default(cuid())
  merchantId String                        // Target merchant
  event      String                        // Event type
  payload    Json                          // Event payload
  delivered  Boolean  @default(false)      // Delivery status
  attempts   Int      @default(0)          // Delivery attempts
  lastError  String?                       // Last error message
  createdAt  DateTime @default(now())
}

Fields:

FieldTypeDescription
idString (CUID)Primary key
merchantIdStringTarget merchant ID
eventStringEvent type (escrow.created, etc.)
payloadJsonFull event payload
deliveredBooleanWhether delivery succeeded
attemptsIntNumber of delivery attempts
lastErrorString?Last error if delivery failed
createdAtDateTimeEvent creation timestamp

6. API Reference

6.1 Authentication

API Key Authentication (Server-to-Server)

Header: X-API-Key: ys_live_xxxxxxxxxxxx

Used for programmatic API access from integrated platforms.

JWT Authentication (Dashboard)

Header: Authorization: Bearer <jwt_token>

Used for dashboard sessions. Tokens issued on login, valid for 7 days.

Admin Authentication

Header: X-Admin-Key: <admin_api_key>

Used for platform administration endpoints.

6.2 Merchant Endpoints (/v1/merchants)

MethodEndpointDescriptionAuth
POST/registerRegister new merchant-
POST/loginLogin (get JWT)-
GET/meGet current profileJWT
PUT/webhookSet webhook URLJWT
POST/keysGenerate API keyJWT
DELETE/keys/:idRevoke API keyJWT

POST /v1/merchants/register

Request:

json
{
  "name": "Acme Marketplace",
  "email": "admin@acme.com",
  "password": "securePassword123"
}

Response (201):

json
{
  "success": true,
  "message": "Merchant registered successfully",
  "data": {
    "merchant": {
      "id": "clxxx...",
      "name": "Acme Marketplace",
      "email": "admin@acme.com"
    },
    "apiKey": {
      "key": "ys_live_abc123def456...",
      "prefix": "ys_live_abc1"
    },
    "token": "eyJhbGciOiJIUzI1..."
  }
}

POST /v1/merchants/login

Request:

json
{
  "email": "admin@acme.com",
  "password": "securePassword123"
}

Response (200):

json
{
  "success": true,
  "message": "Login successful",
  "data": {
    "merchant": {
      "id": "clxxx...",
      "name": "Acme Marketplace",
      "email": "admin@acme.com"
    },
    "token": "eyJhbGciOiJIUzI1..."
  }
}

GET /v1/merchants/me

Response (200):

json
{
  "success": true,
  "data": {
    "id": "clxxx...",
    "name": "Acme Marketplace",
    "email": "admin@acme.com",
    "webhookUrl": "https://acme.com/webhooks/yebosafe",
    "isActive": true,
    "createdAt": "2026-03-19T10:00:00.000Z",
    "apiKeys": [
      {
        "id": "clyyy...",
        "prefix": "ys_live_abc1",
        "name": "Production Key",
        "createdAt": "2026-03-19T10:00:00.000Z",
        "lastUsedAt": "2026-03-19T12:00:00.000Z"
      }
    ]
  }
}

PUT /v1/merchants/webhook

Request:

json
{
  "webhookUrl": "https://acme.com/webhooks/yebosafe"
}

Response (200):

json
{
  "success": true,
  "message": "Webhook URL updated",
  "data": {
    "id": "clxxx...",
    "webhookUrl": "https://acme.com/webhooks/yebosafe"
  }
}

POST /v1/merchants/keys

Request:

json
{
  "name": "Staging Key"
}

Response (201):

json
{
  "success": true,
  "message": "API key created. Save this — it will only be shown once.",
  "data": {
    "key": "ys_live_xyz789...",
    "prefix": "ys_live_xyz7"
  }
}

6.3 Escrow Endpoints (/v1/escrow)

MethodEndpointDescriptionAuth
POST/Create escrowAPI Key / JWT
GET/List escrowsAPI Key / JWT
GET/:idGet escrow by IDAPI Key / JWT
POST/:id/acceptAccept escrowAPI Key / JWT
POST/:id/refuseRefuse escrowAPI Key / JWT
POST/:id/completeComplete escrowAPI Key / JWT
POST/:id/disputeDispute escrowAPI Key / JWT
POST/:id/cancelCancel escrowAPI Key / JWT
GET/lookup/:codeLookup by code (public)-

POST /v1/escrow

Request:

json
{
  "amount": 150.00,
  "currency": "USD",
  "description": "Payment for laptop purchase",
  "payerName": "John Doe",
  "payerEmail": "john@example.com",
  "payerPhone": "+1234567890",
  "metadata": {
    "orderId": "ORD-12345",
    "productId": "PROD-67890"
  },
  "webhookUrl": "https://acme.com/webhooks/order-12345"
}

Response (201):

json
{
  "success": true,
  "message": "Escrow created",
  "data": {
    "id": "clzzz...",
    "reference": "clabc...",
    "amount": "150.00",
    "currency": "USD",
    "description": "Payment for laptop purchase",
    "payerName": "John Doe",
    "payerEmail": "john@example.com",
    "payerPhone": "+1234567890",
    "status": "PENDING",
    "completionCode": "847291",
    "metadata": { "orderId": "ORD-12345", "productId": "PROD-67890" },
    "webhookUrl": "https://acme.com/webhooks/order-12345",
    "createdAt": "2026-03-19T12:00:00.000Z",
    "logs": [
      {
        "id": "cllog...",
        "action": "created",
        "createdAt": "2026-03-19T12:00:00.000Z",
        "metadata": { "amount": 150.00, "currency": "USD" }
      }
    ]
  }
}

GET /v1/escrow

Query Parameters:

  • status - Filter by status (PENDING, ACCEPTED, COMPLETED, etc.)
  • page - Page number (default: 1)
  • limit - Items per page (default: 20)

Response (200):

json
{
  "success": true,
  "data": {
    "escrows": [...],
    "total": 42,
    "page": 1,
    "limit": 20,
    "pages": 3
  }
}

GET /v1/escrow/:id

Response (200):

json
{
  "success": true,
  "data": {
    "id": "clzzz...",
    "reference": "clabc...",
    "amount": "150.00",
    "currency": "USD",
    "status": "PENDING",
    "completionCode": "847291",
    "logs": [...]
  }
}

POST /v1/escrow/:id/accept

Response (200):

json
{
  "success": true,
  "message": "Escrow accepted",
  "data": {
    "id": "clzzz...",
    "status": "ACCEPTED"
  }
}

POST /v1/escrow/:id/refuse

Request:

json
{
  "reason": "Item out of stock"
}

Response (200):

json
{
  "success": true,
  "message": "Escrow refused",
  "data": {
    "id": "clzzz...",
    "status": "REFUSED"
  }
}

POST /v1/escrow/:id/complete

Request:

json
{
  "completionCode": "847291"
}

Response (200):

json
{
  "success": true,
  "message": "Escrow completed — funds released to wallet",
  "data": {
    "id": "clzzz...",
    "status": "COMPLETED",
    "completedAt": "2026-03-19T14:30:00.000Z"
  }
}

POST /v1/escrow/:id/dispute

Request:

json
{
  "reason": "Item not as described"
}

Response (200):

json
{
  "success": true,
  "message": "Dispute raised",
  "data": {
    "id": "clzzz...",
    "status": "DISPUTED",
    "disputedAt": "2026-03-19T14:45:00.000Z",
    "disputeReason": "Item not as described"
  }
}

POST /v1/escrow/:id/cancel

Request:

json
{
  "reason": "Buyer requested cancellation"
}

Response (200):

json
{
  "success": true,
  "message": "Escrow cancelled",
  "data": {
    "id": "clzzz...",
    "status": "CANCELLED",
    "cancelledAt": "2026-03-19T14:50:00.000Z",
    "cancelReason": "Buyer requested cancellation"
  }
}

GET /v1/escrow/lookup/:code

Public endpoint - no authentication required.

Response (200):

json
{
  "success": true,
  "data": {
    "id": "clzzz...",
    "reference": "clabc...",
    "amount": 150.00,
    "currency": "USD",
    "description": "Payment for laptop purchase",
    "status": "ACCEPTED",
    "createdAt": "2026-03-19T12:00:00.000Z",
    "merchantName": "Acme Marketplace"
  }
}

6.4 Wallet Endpoints (/v1/wallet)

MethodEndpointDescriptionAuth
GET/Get wallet & transactionsAPI Key / JWT
POST/withdrawRequest withdrawalAPI Key / JWT

GET /v1/wallet

Query Parameters:

  • page - Page number (default: 1)
  • limit - Items per page (default: 20)

Response (200):

json
{
  "success": true,
  "data": {
    "balance": 1250.50,
    "currency": "USD",
    "transactions": [
      {
        "id": "clentry...",
        "type": "CREDIT",
        "amount": 150.00,
        "description": "Escrow completed: clzzz...",
        "reference": "clzzz...",
        "createdAt": "2026-03-19T14:30:00.000Z"
      }
    ],
    "total": 15,
    "page": 1,
    "limit": 20,
    "pages": 1
  }
}

POST /v1/wallet/withdraw

Request:

json
{
  "amount": 500.00,
  "bankDetails": {
    "bankName": "First National Bank",
    "accountNumber": "1234567890",
    "accountName": "Acme Inc"
  }
}

Response (200):

json
{
  "success": true,
  "message": "Withdrawal request submitted",
  "data": {
    "balance": 750.50
  }
}

6.5 Admin Endpoints (/v1/admin)

MethodEndpointDescriptionAuth
GET/escrowsList all escrowsAdmin Key
GET/merchantsList all merchantsAdmin Key
GET/statsPlatform statisticsAdmin Key

GET /v1/admin/escrows

Query Parameters:

  • status - Filter by status
  • page - Page number
  • limit - Items per page

Response (200):

json
{
  "success": true,
  "data": {
    "escrows": [
      {
        "id": "clzzz...",
        "reference": "clabc...",
        "amount": "150.00",
        "status": "COMPLETED",
        "merchant": {
          "id": "clxxx...",
          "name": "Acme Marketplace",
          "email": "admin@acme.com"
        }
      }
    ],
    "total": 156,
    "page": 1,
    "limit": 20,
    "pages": 8
  }
}

GET /v1/admin/merchants

Response (200):

json
{
  "success": true,
  "data": {
    "merchants": [
      {
        "id": "clxxx...",
        "name": "Acme Marketplace",
        "email": "admin@acme.com",
        "isActive": true,
        "createdAt": "2026-03-19T10:00:00.000Z",
        "wallet": {
          "balance": "1250.50",
          "currency": "USD"
        },
        "_count": {
          "escrows": 42
        }
      }
    ],
    "total": 15,
    "page": 1,
    "limit": 20,
    "pages": 1
  }
}

GET /v1/admin/stats

Response (200):

json
{
  "success": true,
  "data": {
    "totalEscrows": 1542,
    "completedEscrows": 1287,
    "activeEscrows": 89,
    "merchantCount": 45,
    "totalVolume": 256789.50,
    "completionRate": 83
  }
}

7. Escrow Lifecycle State Machine

                    ┌─────────────┐
                    │   PENDING   │
                    │  (created)  │
                    └──────┬──────┘

           ┌───────────────┼───────────────┐
           │               │               │
           ▼               ▼               ▼
    ┌──────────┐    ┌──────────┐    ┌──────────┐
    │ ACCEPTED │    │ REFUSED  │    │CANCELLED │
    │(seller ok)│   │(seller no)│   │  (abort) │
    └────┬─────┘    └──────────┘    └──────────┘

    ┌────┼────────────┐
    │    │            │
    ▼    ▼            ▼
┌───────────┐  ┌──────────┐
│ COMPLETED │  │ DISPUTED │
│(code used)│  │ (issue)  │
└───────────┘  └────┬─────┘

           ┌────────┼────────┐
           ▼                 ▼
    ┌──────────┐      ┌──────────┐
    │ REFUNDED │      │ RESOLVED │
    │ (future) │      │ (future) │
    └──────────┘      └──────────┘

State Transitions

FromToActionRequirements
PENDINGACCEPTEDacceptMerchant decides to fulfill
PENDINGREFUSEDrefuseReason required
PENDINGCANCELLEDcancelReason required
PENDINGDISPUTEDdisputeReason required
ACCEPTEDCOMPLETEDcompleteValid completion code
ACCEPTEDDISPUTEDdisputeReason required
ACCEPTEDCANCELLEDcancelReason required

Terminal States

  • COMPLETED: Funds released, escrow closed
  • REFUSED: Merchant declined, no action needed
  • CANCELLED: Transaction aborted
  • DISPUTED: Requires manual resolution
  • REFUNDED: Funds returned (future implementation)

8. Payment Integration

8.1 Current Implementation

YeboSafe currently operates as an escrow ledger system:

  1. No direct payment collection - Funds are assumed to be collected by the integrating platform
  2. Wallet-based accounting - Completed escrows credit the merchant's virtual wallet
  3. Manual withdrawals - Withdrawal requests create debit entries; actual payouts handled externally

8.2 Integration Pattern

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Platform  │────▶│  YeboSafe   │────▶│  Merchant   │
│  (collects  │     │  (escrow    │     │  (receives  │
│   payment)  │     │   ledger)   │     │   payout)   │
└─────────────┘     └─────────────┘     └─────────────┘
        │                  │                   ▲
        │                  │                   │
        ▼                  ▼                   │
   Stripe/PayStack    Wallet Balance    Manual/API Payout

8.3 Future Integration Points

ProviderRegionStatus
StripeGlobalPlanned
PayStackNigeria, Ghana, SAPlanned
FlutterwavePan-AfricanPlanned
MTN MoMoUganda, Rwanda, etc.Planned
M-PesaKenya, TanzaniaPlanned

Future Flow:

1. Escrow Created → YeboSafe initiates payment hold via provider
2. Escrow Accepted → Funds confirmed in escrow account
3. Escrow Completed → Funds released to merchant's external account
4. Escrow Refunded → Funds returned to payer

9. Dispute Resolution

9.1 Current Implementation

  • Dispute Flag: Any party can mark escrow as DISPUTED
  • Reason Required: Dispute reason captured and logged
  • Manual Resolution: Admin reviews via dashboard or direct database access
  • Audit Trail: Full log of all actions available for review

9.2 Dispute Workflow

1. Party raises dispute (POST /escrow/:id/dispute)
2. Escrow status → DISPUTED
3. Webhook fires: escrow.disputed
4. Admin reviews:
   - Transaction details
   - Payer information
   - Full audit log
   - Communication history (via integrated platform)
5. Resolution options:
   - Release to merchant (manual COMPLETED)
   - Refund to payer (manual REFUNDED - future)
   - Partial settlement (future)

9.3 Future Enhancements

FeatureDescriptionStatus
Dispute DashboardDedicated UI for reviewing disputesPlanned
Evidence UploadAllow parties to submit evidencePlanned
Resolution ActionsAPI endpoints for admin resolutionsPlanned
Auto-EscalationTime-based escalation rulesPlanned
ArbitrationThird-party arbitration integrationFuture

10. Webhooks

10.1 Event Types

EventDescriptionTrigger
escrow.createdNew escrow createdPOST /escrow
escrow.acceptedMerchant accepted escrowPOST /escrow/:id/accept
escrow.refusedMerchant refused escrowPOST /escrow/:id/refuse
escrow.completedEscrow completed, funds releasedPOST /escrow/:id/complete
escrow.disputedDispute raisedPOST /escrow/:id/dispute
escrow.cancelledEscrow cancelledPOST /escrow/:id/cancel

10.2 Webhook Payload

json
{
  "event": "escrow.completed",
  "data": {
    "id": "clzzz...",
    "reference": "clabc...",
    "amount": "150.00",
    "currency": "USD",
    "status": "COMPLETED"
  },
  "timestamp": "2026-03-19T14:30:00.000Z"
}

10.3 Webhook Security

Signature Verification:

javascript
// Header: X-YeboSafe-Signature
const signature = crypto
  .createHmac('sha256', process.env.WEBHOOK_SECRET)
  .update(requestBody)
  .digest('hex');

if (req.headers['x-yebosafe-signature'] !== signature) {
  throw new Error('Invalid signature');
}

10.4 Delivery & Retry

AttemptDelayTimeout
1Immediate10s
22 seconds10s
34 seconds10s

Delivery Status Tracking:

  • WebhookEvent model tracks delivery status
  • Failed attempts logged with error message
  • Retry up to 3 times with exponential backoff

10.5 Webhook URL Override

Per-escrow webhook URL can be specified:

json
{
  "amount": 100.00,
  "webhookUrl": "https://acme.com/webhooks/order-12345"
}

11. Service Architecture

11.1 Component Overview

┌─────────────────────────────────────────────────────────────────┐
│                         API Layer                                │
│  /v1/merchants  /v1/escrow  /v1/wallet  /v1/admin               │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                       Middleware                                 │
│  apiKeyAuth | jwtAuth | flexAuth | adminAuth | rateLimiter      │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                      Controllers                                 │
│  merchant.controller | escrow.controller | wallet.controller    │
│  admin.controller                                               │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                       Services                                   │
│                                                                 │
│  ┌──────────────────┐  ┌──────────────────┐                    │
│  │ MerchantService  │  │  EscrowService   │                    │
│  │ - register       │  │ - createEscrow   │                    │
│  │ - login          │  │ - acceptEscrow   │                    │
│  │ - generateApiKey │  │ - completeEscrow │                    │
│  │ - revokeApiKey   │  │ - disputeEscrow  │                    │
│  └──────────────────┘  │ - cancelEscrow   │                    │
│                        │ - adminStats     │                    │
│                        └──────────────────┘                    │
│                                                                 │
│  ┌──────────────────┐  ┌──────────────────┐                    │
│  │  WalletService   │  │  WebhookService  │                    │
│  │ - ensureWallet   │  │ - fire           │                    │
│  │ - creditWallet   │  │ - deliver        │                    │
│  │ - debitWallet    │  │ - sign           │                    │
│  │ - getWallet      │  │ - retry          │                    │
│  └──────────────────┘  └──────────────────┘                    │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                    Data Layer (Prisma)                          │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                PostgreSQL (Neon)                         │   │
│  │  Merchant | ApiKey | EscrowTransaction | EscrowLog      │   │
│  │  MerchantWallet | WalletEntry | WebhookEvent            │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

11.2 Request Flow

API Request


Express App (app.ts)

    ├─→ Helmet (security headers)
    ├─→ CORS
    ├─→ JSON Parser
    ├─→ Morgan (logging)
    ├─→ Rate Limiter


Route Handler

    ├─→ Auth Middleware (apiKeyAuth / jwtAuth / flexAuth)
    ├─→ Validation (express-validator)


Controller

    ├─→ Input validation
    ├─→ Service method call


Service

    ├─→ Business logic
    ├─→ Prisma queries
    ├─→ Cross-service calls (Wallet, Webhook)


Response

11.3 Service Dependencies

EscrowService
    ├─→ WalletService (on completion)
    └─→ WebhookService (on status change)

MerchantService
    └─→ WalletService (create wallet on register)

WebhookService
    └─→ Prisma (log delivery status)

12. Authentication & Security

12.1 Authentication Methods

MethodUse CaseHeaderValidity
API KeyServer-to-serverX-API-KeyUntil revoked
JWTDashboard sessionsAuthorization: Bearer7 days
Admin KeyPlatform administrationX-Admin-KeyStatic

12.2 Flexible Auth

Routes using flexAuth middleware accept either API Key or JWT:

typescript
const flexAuth = (req, res, next) => {
  if (req.headers['x-api-key']) {
    return apiKeyAuth(req, res, next);
  } else if (req.headers['authorization']) {
    return jwtAuth(req, res, next);
  }
  return res.status(401).json({ message: 'Authentication required' });
};

12.3 Rate Limiting

  • Limit: 200 requests per minute per IP
  • Implementation: In-memory (should use Redis for production scale)
  • Response when exceeded: 429 Too Many Requests

12.4 Security Headers

Via Helmet middleware:

  • Content Security Policy
  • X-Frame-Options
  • X-Content-Type-Options
  • Strict-Transport-Security

12.5 Password Security

  • Hashing: bcrypt with cost factor 12
  • Minimum length: 8 characters (enforced at registration)

12.6 API Key Format

ys_live_{uuid-without-dashes}

Example: ys_live_abc123def456ghi789jkl012mno345
  • Prefix identifies the key type
  • First 12 characters stored as prefix for safe display
  • Full key shown only once at creation

13. Environment Configuration

13.1 Required Variables

bash
# Database
DATABASE_URL=postgresql://user:pass@host:5432/yebosafe

# Authentication
JWT_SECRET=your-secure-jwt-secret

# Admin Access
ADMIN_API_KEY=your-admin-api-key

# Runtime
NODE_ENV=production
PORT=8080

13.2 Optional Variables

bash
# Logging
LOG_LEVEL=info

# External Services (future)
STRIPE_SECRET_KEY=sk_live_xxx
PAYSTACK_SECRET_KEY=sk_live_xxx

14. Gaps & Missing Features

14.1 Critical Missing

FeatureDescriptionPriority
Payment Gateway IntegrationDirect payment collection/disbursementP0
Dashboard RoutingReact Router setup in App.tsxP0
Payout SystemAutomated withdrawals to bank/mobile moneyP0
Dispute Resolution UIAdmin interface for dispute handlingP1
Email NotificationsTransactional emails to merchants/payersP1

14.2 Security Enhancements

FeatureDescriptionPriority
Redis Rate LimiterReplace in-memory rate limiterP1
API Key HashingStore hashed keys instead of plaintextP1
Webhook Secret per MerchantIndividual signing secretsP2
Audit Log APIExpose logs via APIP2
2FA for DashboardTwo-factor authenticationP2

14.3 Operational Features

FeatureDescriptionPriority
Health Check DashboardMonitoring and alertingP1
Metrics/AnalyticsPrometheus/Grafana integrationP2
API Documentation (OpenAPI)Swagger/OpenAPI specP1
SDK LibrariesNode.js, Python, PHP SDKsP2
Sandbox/Test ModeTest environment without real transactionsP1

14.4 Business Features

FeatureDescriptionPriority
Platform FeesAutomatic fee deductionP1
Multi-Currency WalletsSeparate balances per currencyP2
Partial ReleasesRelease portion of escrowP2
Scheduled ReleasesTime-based auto-releaseP3
Escrow TemplatesReusable escrow configurationsP3
Sub-Merchant SupportHierarchical merchant structureP3

14.5 Dashboard Gaps

IssueDescription
No routing configuredApp.tsx shows Vite boilerplate, not routes
Settings page missingSettings nav link leads nowhere
No deploymentDashboard not deployed to Cloudflare Pages
Missing responsive designMobile layout not optimized

15. Comparison: YeboShops vs YeboSafe

AspectYeboShops SecurePaymentYeboSafe Escrow
Target UserEnd consumers (buyers/sellers)Platform developers
IntegrationBuilt-in to YeboShopsStandalone API
AuthUser JWTMerchant API Key + JWT
Wallet OwnerIndividual usersMerchants (platforms)
Completion CodeBuyer holds, shares with sellerPlatform controls distribution
WebhooksInternal notificationsExternal HTTP callbacks
Multi-TenancySingle platformMultiple platforms
AdminYeboShops adminYeboSafe admin

16. Integration Example

16.1 Creating an Escrow (Node.js)

javascript
const YEBOSAFE_API = 'https://yebosafe-api-1009635282906.europe-west1.run.app';
const API_KEY = 'ys_live_your_key_here';

async function createEscrow(orderDetails) {
  const response = await fetch(`${YEBOSAFE_API}/v1/escrow`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
    },
    body: JSON.stringify({
      amount: orderDetails.total,
      currency: 'USD',
      description: `Order #${orderDetails.orderId}`,
      payerName: orderDetails.buyerName,
      payerEmail: orderDetails.buyerEmail,
      metadata: {
        orderId: orderDetails.orderId,
        items: orderDetails.items,
      },
    }),
  });

  const { data } = await response.json();
  
  // Store completion code securely
  // Share code with buyer via your platform
  return data;
}

16.2 Handling Webhooks

javascript
const crypto = require('crypto');

app.post('/webhooks/yebosafe', express.json(), (req, res) => {
  // Verify signature
  const signature = crypto
    .createHmac('sha256', process.env.YEBOSAFE_WEBHOOK_SECRET)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (req.headers['x-yebosafe-signature'] !== signature) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event, data } = req.body;

  switch (event) {
    case 'escrow.accepted':
      // Notify seller to fulfill order
      notifySellerToShip(data.reference);
      break;
    case 'escrow.completed':
      // Mark order as complete
      markOrderComplete(data.reference);
      break;
    case 'escrow.disputed':
      // Alert support team
      alertSupportTeam(data.reference, data.disputeReason);
      break;
  }

  res.json({ received: true });
});

16.3 Completing an Escrow

javascript
async function completeEscrow(completionCode) {
  const response = await fetch(`${YEBOSAFE_API}/v1/escrow/${escrowId}/complete`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
    },
    body: JSON.stringify({ completionCode }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message);
  }

  return response.json();
}

Appendix A: Error Codes

CodeHTTP StatusDescription
ESCROW_NOT_FOUND404Escrow does not exist or not owned by merchant
INVALID_AMOUNT400Amount must be greater than 0
INVALID_CODE404Completion code does not match any escrow
ALREADY_COMPLETED400Escrow already completed
ALREADY_DISPUTED400Escrow already disputed
ALREADY_CANCELLED400Escrow already cancelled
ESCROW_NOT_PENDING400Escrow is not in PENDING status
ESCROW_NOT_ACCEPTED400Escrow must be ACCEPTED before completing
ESCROW_DISPUTED400Cannot act on a disputed escrow
ESCROW_CANCELLED400Escrow is cancelled
ESCROW_ALREADY_CLOSED400Escrow is in terminal state
EMAIL_TAKEN409Email already registered
KEY_NOT_FOUND404API key not found or not owned
INSUFFICIENT_BALANCE400Wallet balance insufficient for withdrawal
INVALID_CREDENTIALS401Login email or password incorrect

Appendix B: Webhook Events Reference

typescript
interface WebhookPayload {
  event: 'escrow.created' | 'escrow.accepted' | 'escrow.completed' 
       | 'escrow.refused' | 'escrow.disputed' | 'escrow.cancelled';
  data: {
    id: string;
    reference: string;
    amount: string;
    currency: string;
    status: EscrowStatus;
  };
  timestamp: string;  // ISO 8601
}

Appendix C: Dashboard Pages

PageRouteDescription
Login/loginMerchant authentication
Register/registerMerchant registration
Dashboard/dashboardOverview with stats
Escrows/escrowsEscrow list with filters
Escrow Detail/escrows/:idSingle escrow view with actions
Wallet/walletBalance and transactions
Developers/developersAPI keys and webhooks
Settings/settingsAccount settings (not implemented)

Document generated from YeboSafe codebase analysis (yebosafe-api, yebosafe-dashboard) on 2026-03-19.

One chat. Everything done.