Escrow Safe Trade is live: protect your next high-value deal Learn more
ProductsPayment links & checkoutM-Pesa, cards, invoices, split payoutsEscrow Safe TradeMilestone and release-based escrowWallets & P2PPersonal and business walletsChama, Lengo & DeniGroup savings, goals and IOUsBusiness toolkitAccounting, payroll, POS, ticketingBulk payments & payrollPay many people in one uploadSubscriptions & billingRecurring plans and renewalsRent, loans & donationsCollections and campaignsAll products
SolutionsMerchantsBuyersChama groupsBusiness solutionsIndustries
Pricing
DevelopersDeveloper overviewQuickstart, auth and sandboxREST APIsCheckout, invoices and requestsWebhooksSigned, real-time eventsWallet-as-a-ServiceVirtual sub-accountsDocumentationGuides and API reference
Security
CompanyAbout usCareersPartnersBlogHelp centreContact us
Log in Get started Talk to sales
Wallets & money movement

Wallet-as-a-Service (WaaS)

Give each of your end users a virtual account while real money sits in one master business wallet. Covers accounts, deposits, withdrawals, transfers and events.

Base URLhttps://api.paysasa.com/api/v1Authx-api-keyFormatJSON

WaaS uses one master business wallet on Paysasa while tracking virtual sub-accounts for your end-users. Real money moves only through the master wallet; you maintain your own internal user balances and update them from WaaS webhooks.

Enable WaaS under PATCH /businesses/:id/waas (dashboard session or API key). All WaaS routes require business_id when using API keys. Business must be verified (default gate) — see Compliance & Trust.

Deposit channels: toggle manual_deposits_enabled (PayBill / SIM toolkit to VA ref) and api_deposits_enabled (intent / checkout / STK). Both default on. When manual is off, C2B validation rejects and confirmation refunds via M-Pesa Transaction Reversal (B2C fallback to payer).

Optional VA display_name can appear in owner deposit and withdrawal SMS when those notification events are enabled.

Each VA has two PayBill account references customers can use on M-Pesa: mpesa_account_number (e.g. WAAS-MATHEW) and serial_account_number (e.g. 12345678). Both credit the same virtual account on C2B confirmation.

Merchant visibility: Use GET /businesses/:id/waas for master wallet balance, spendable, reserved, and deposit-channel flags. Use GET /waas/virtual-accounts?include_ledger=1 for each end-user attributed balance, and GET /waas/transactions?external_id=… for per-user deposit/withdrawal history.

API endpoints#

MethodPathDescription
GET/PATCH/businesses/:id/waasWaaS settings (enable, reserved, manual/API deposit toggles)
GET/POST/waas/virtual-accountsList/create VAs (?include_ledger=1). Body: external_id, display_name?, display_identifier?. Response includes mpesa_account_number + serial_account_number
GET/PATCH/waas/virtual-accounts/:idGet/update VA (status, display_name, display_identifier; ?include_transactions=1)
POST/waas/deposits/intentTime-bound PayBill deposit intent
GET/waas/deposits/intent/:idDeposit intent status
POST/waas/deposits/checkoutHosted checkout session (wallet + M-Pesa)
POST/waas/deposits/stkCheckout + one-shot M-Pesa STK
POST/waas/withdrawWithdraw to M-Pesa or Paysasa wallet (fee_bearer: master|va)
GET/waas/withdraw/quoteQuote fee + debits (?amount=&fee_bearer=)
GET/waas/withdraw/statusPoll withdrawal (?transaction_id= or ?reference=)
GET/waas/transactionsWaaS tx history (?external_id=, ?virtual_account_id=, ?type=)
GET/waas/reconciliationMaster wallet vs aggregates snapshot
POST/waas/sandbox/simulate-depositSandbox-only simulated C2B (test API keys)
cURL
curl "https://api.paysasa.com/api/v1/businesses/biz_xxxxxxxxxxxxxxxx/waas" \
  -H "x-api-key: apk_live_<prefix>.<secret>"
# → waas: { enabled, balance_minor, spendable_balance_minor, reserved_balance_minor,
#           manual_deposits_enabled, api_deposits_enabled, ... }
cURL
# List virtual accounts with attributed balances
curl "https://api.paysasa.com/api/v1/waas/virtual-accounts?business_id=biz_xxxxxxxxxxxxxxxx&include_ledger=1&limit=50" \
  -H "x-api-key: apk_live_<prefix>.<secret>"
# virtual_accounts[].mpesa_account_number → e.g. WAAS-MATHEW
# virtual_accounts[].serial_account_number → e.g. 22457777 (numeric PayBill ref)
cURL
# Single virtual account + balance + transactions
curl "https://api.paysasa.com/api/v1/waas/virtual-accounts/va_xxxxxxxxxxxxxxxx?business_id=biz_xxxxxxxxxxxxxxxx&include_ledger=1&include_transactions=1&tx_limit=50" \
  -H "x-api-key: apk_live_<prefix>.<secret>"
cURL
curl -X PATCH "https://api.paysasa.com/api/v1/waas/virtual-accounts/va_xxxxxxxxxxxxxxxx" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "status": "suspended",
    "display_identifier": "254712345678",
    "display_name": "Mathew Doe"
  }'
cURL
curl -X PATCH "https://api.paysasa.com/api/v1/businesses/biz_xxxxxxxxxxxxxxxx/waas" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "reserved_balance_minor": 100000,
    "manual_deposits_enabled": true,
    "api_deposits_enabled": true
  }'
cURL
curl -X POST "https://api.paysasa.com/api/v1/waas/virtual-accounts" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "external_id": "pollsoko_user_992",
    "display_name": "Mathew Doe",
    "display_identifier": "254712345678"
  }'
# → virtual_account: {
#      mpesa_account_number: "WAAS-POLLSOKO_USER_992",
#      serial_account_number: "22457777",
#      ...
#    }
cURL
curl -X POST "https://api.paysasa.com/api/v1/waas/deposits/intent" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "external_id": "pollsoko_user_992",
    "amount": 50000,
    "ttl_minutes": 30
  }'
cURL
# Hosted checkout (STK on /checkout/{sessionId} or server-side STK below)
curl -X POST "https://api.paysasa.com/api/v1/waas/deposits/checkout" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "external_id": "pollsoko_user_992",
    "amount": 50000,
    "customer_phone": "254712345678",
    "success_url": "https://pollsoko.com/wallet/topup/success"
  }'
cURL
# One-shot: create checkout session + trigger M-Pesa STK push
curl -X POST "https://api.paysasa.com/api/v1/waas/deposits/stk" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "external_id": "pollsoko_user_992",
    "amount": 50000,
    "phone": "254712345678"
  }'
cURL
# Withdraw to M-Pesa (B2C)
curl -X POST "https://api.paysasa.com/api/v1/waas/withdraw" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "x-idempotency-key: wdrl-unique-key-001" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "virtual_account_id": "va_xxxxxxxxxxxxxxxx",
    "amount": 100000,
    "destination_type": "mpesa",
    "destination_phone": "254712345678"
  }'
cURL
# Withdraw to a Paysasa user wallet (settles as an in-platform transfer)
curl -X POST "https://api.paysasa.com/api/v1/waas/withdraw" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "x-idempotency-key: wdrl-wallet-001" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "source_identifier": "pollsoko_user_992",
    "amount": 100000,
    "destination_type": "wallet",
    "destination_wallet": "254712345678"
  }'
cURL
curl "https://api.paysasa.com/api/v1/waas/withdraw/status?business_id=biz_xxxxxxxxxxxxxxxx&transaction_id=tx_xxxxxxxxxxxxxxxx" \
  -H "x-api-key: apk_live_<prefix>.<secret>"
cURL
# All WaaS transactions, or filter by end-user
curl "https://api.paysasa.com/api/v1/waas/transactions?business_id=biz_xxxxxxxxxxxxxxxx&external_id=pollsoko_user_992&type=waas_deposit&limit=25" \
  -H "x-api-key: apk_live_<prefix>.<secret>"

# Or filter by virtual_account_id
curl "https://api.paysasa.com/api/v1/waas/transactions?business_id=biz_xxxxxxxxxxxxxxxx&virtual_account_id=va_xxxxxxxxxxxxxxxx&limit=25" \
  -H "x-api-key: apk_live_<prefix>.<secret>"
cURL
curl "https://api.paysasa.com/api/v1/waas/reconciliation?business_id=biz_xxxxxxxxxxxxxxxx" \
  -H "x-api-key: apk_live_<prefix>.<secret>"
cURL
# Sandbox API keys only — simulates C2B without real M-Pesa
curl -X POST "https://api.paysasa.com/api/v1/waas/sandbox/simulate-deposit" \
  -H "x-api-key: apk_test_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "external_id": "pollsoko_user_992",
    "amount": 50000
  }'

Webhook events: waas.deposit.success, waas.withdrawal.success, and failure variants. Withdrawal webhooks include destination_type (mpesa or wallet).