Escrows

Overview

This guide walks you through the full lifecycle of a Cashless escrow transaction and seller balance management. The process is asynchronous and event-driven.

Core Concepts

An Escrow's status is controlled by the status of its associated Transactions:

  • Initiating a charge creates an escrow-deposit transaction.
  • Initiating a payout creates an escrow-payout transaction.
  • Initiating a refund creates an escrow-refund transaction.

When a transaction changes state, it automatically updates the parent Escrow's status and triggers the escrow_updated webhook.

Seller Balance System

If an escrow includes a sellerId, seller balances track automatically:

  • Charge successful: Seller balance increases by the escrow amount
  • Payout successful: Seller balance decreases by the payout amount
  • Refund successful: Seller balance decreases by the refund amount

Escrow Transaction Flow

Step 1: Create the Escrow

POST /api/production/v1/escrows

{
  "amount": 100,
  "chargeCurrency": "KES",
  "receiverCurrency": "KES",
  "externalReference": "test1f3256",
  "sellerId": "seller-uuid-here",
  "chargeDetails": {
    "method": "momo",
    "firstName": "Billy",
    "lastName": "Batson",
    "country": "KE",
    "email": "[email protected]",
    "phoneNumber": "254712345678",
    "momoOperatorId": "mpesa"
  },
  "payoutDetails": {
    "method": "momo",
    "country": "KE",
    "payoutMethod": {
      "accountName": "Virgil Hawkins",
      "accountNumber": "254712345678"
    }
  }
}

Save the id from the response — this is your escrowId.

Step 2: Fund the Escrow

POST /charges/{escrowId} — initiates the charge. Status changes to charge_initiated.

Step 3: Listen for Charge Result

Watch for the escrow_updated webhook. If status is charge_successful, funds are held and you can proceed to disburse.

Step 4: Disburse Funds

Payout: POST /payouts/{escrowId} — full or partial payout, optionally override payout details.

Refund: POST /refunds/{escrowId} — full or partial refund to the sender.

Step 5: Monitor Disbursement & Balance

Check both status and balance in the escrow_updated webhook to know if the escrow still has funds.

Step 6: Repeat or Conclude

  • balance > 0: Escrow still active — you can initiate another payout or refund.
  • balance = 0: Lifecycle complete.

Step 7: (Optional) Close the Escrow

POST /escrows/{id}/close — irreversible. Requires zero balance and all transactions settled.

Escrow Status Values

  • pending_chargecharge_initiatedcharge_successful / charge_failed
  • payout_initiatedpayout_successful / payout_failed
  • refund_initiatedrefund_successful / refund_failed
  • closed

Seller Balance Management

Create a Seller

POST /api/production/v1/escrows/sellers

{
  "name": "John's Coffee Shop",
  "email": "[email protected]",
  "currency": "KES",
  "defaultPayoutDetails": {
    "method": "momo",
    "country": "KE",
    "payoutMethod": {
      "accountName": "John Doe",
      "accountNumber": "254712345678"
    }
  }
}

Sellers can hold multi-currency balances:

{
  "aggregatedBalance": {
    "KES": 1250.50,
    "USD": 75.25,
    "NGN": 45000.00
  }
}

Query Seller

  • GET /api/production/v1/escrows/sellers/{sellerId} — details + balance
  • GET /api/production/v1/escrows/sellers — list all sellers
  • GET /api/production/v1/escrows/sellers/{sellerId}/transactions — transaction history

Webhook: escrow_updated

{
  "event": "escrow_updated",
  "data": {
    "id": "escrow-id-123",
    "publicKey": "user-public-key",
    "status": "charge_successful",
    "balance": 100.0,
    "externalReference": "order-ABC-456"
  }
}