Skip to Content

Payouts API

Send payments to till/paybill numbers and mobile money wallets across Africa.

Mobile Money Transfer

Initiate a payout to a mobile money wallet.

POST /business/payout/transfer

Mobile Money Request Body

FieldTypeRequiredDescription
senderCurrencystringYesSender’s currency code (USD, EUR, etc.)
receiverCurrencystringYesReceiver’s currency code (KES, TZS, UGX, ZAR)
amountnumberYesAmount to send in receiver’s currency
senderFirstNamestringYesSender’s first name
senderLastNamestringYesSender’s last name
senderDobstringYesSender’s date of birth (YYYY-MM-DD)
senderIdTypestringYesID type: passport, national_id, driving_license
senderIdNumberstringYesSender’s ID number
senderNationalitystringYesSender’s nationality (2-letter country code)
senderTelephoneNostringYesSender’s phone number with country code
receiverFirstNamestringYesReceiver’s first name
receiverLastNamestringYesReceiver’s last name
receiverPhonestringYesReceiver’s phone number with country code
relationshipstringYesRelationship to receiver (e.g., “Family”, “Business”)
payoutCountrystringYesDestination country code (KE, TZ, UG, ZA)
purposeOfTransferstringYesPurpose (see allowed values below)
sourceOfFundsstringYesSource of funds (see allowed values below)
businessReferencestringYesYour unique transaction reference

Example Request - Kenya Mobile Money

curl -X POST https://api.test.wakapay.io/business/payout/transfer \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "senderCurrency": "USD", "receiverCurrency": "KES", "amount": 100.0, "senderFirstName": "Alice", "senderLastName": "Smith", "senderDob": "1985-03-15", "senderIdType": "passport", "senderIdNumber": "AB1234567", "senderNationality": "TZ", "senderTelephoneNo": "+255712345678", "receiverFirstName": "John", "receiverLastName": "Doe", "receiverPhone": "+254700000001", "relationship": "Family", "payoutCountry": "KE", "purposeOfTransfer": "family_support", "sourceOfFunds": "salary", "businessReference": "TEST-TRANSFER-KE-001" }'
const response = await fetch( "https://api.test.wakapay.io/business/payout/transfer", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ senderCurrency: "USD", receiverCurrency: "KES", amount: 100.0, senderFirstName: "Alice", senderLastName: "Smith", senderDob: "1985-03-15", senderIdType: "passport", senderIdNumber: "AB1234567", senderNationality: "TZ", senderTelephoneNo: "+255712345678", receiverFirstName: "John", receiverLastName: "Doe", receiverPhone: "+254700000001", relationship: "Family", payoutCountry: "KE", purposeOfTransfer: "family_support", sourceOfFunds: "salary", businessReference: "TEST-TRANSFER-KE-001", }), }, ); const data = await response.json(); console.log(data);

Mobile Money Response (201 Created)

{ "businessReference": "TEST-TRANSFER-KE-001", "receiverAmount": 100, "receiverCurrency": "KES", "senderAmount": 0.7751937984496124, "senderCurrency": "USD", "status": "termination_success", "totalDebited": 0.7906976744186047, "wakapayReference": "0ad0a4a6-364b-11f1-8c14-0242ac120008" }

Response Fields

FieldTypeDescription
wakapayReferencestringWakapay transaction ID
businessReferencestringYour reference for tracking
statusstringTransaction status (termination_success, termination_pending, termination_failure)
senderAmountnumberAmount debited from sender wallet
senderCurrencystringSender’s currency
receiverAmountnumberAmount received by recipient
receiverCurrencystringReceiver’s currency
totalDebitednumberTotal amount debited (includes fees)

Payment to Paybill / Till

Initiate a payment to a Paybill or Till number.

POST /business/payout/payment

Difference between Till and Paybill:

  • Till (Lipa Number) - No account reference needed. Payment goes directly to the till number.
  • Paybill - Requires an account reference (receiverAccount). Payment goes to the paybill number with a specific account reference for tracking.

Till/Paybill Request Body

FieldTypeRequiredDescription
typestringYesPayment type: paybill or till
senderCurrencystringYesSender’s currency code (USD, EUR, etc.)
receiverCurrencystringYesReceiver’s currency code (KES, TZS, UGX, ZAR)
amountnumberYesAmount to send in receiver’s currency
senderFirstNamestringYesSender’s first name
senderLastNamestringYesSender’s last name
senderDobstringYesSender’s date of birth (YYYY-MM-DD)
senderIdTypestringYesID type: passport, national_id, driving_license
senderIdNumberstringYesSender’s ID number
senderNationalitystringYesSender’s nationality (2-letter country code)
senderTelephoneNostringYesSender’s phone number with country code
receiverFirstNamestringYesReceiver’s first name
receiverLastNamestringYesReceiver’s last name
receiverLipaNumberstringYesPaybill or Till number
receiverAccountstringConditionalPaybill account/reference (required for paybill, omit for till)
relationshipstringYesRelationship to receiver (e.g., “Family”, “Business”)
payoutCountrystringYesDestination country code (KE, TZ, UG, ZA)
purposeOfTransferstringYesPurpose (see allowed values below)
sourceOfFundsstringYesSource of funds (see allowed values below)
businessReferencestringYesYour unique transaction reference

Example Request - Till Payment

curl -X POST https://api.test.wakapay.io/business/payout/payment \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "type": "till", "senderCurrency": "USD", "receiverCurrency": "KES", "amount": 100.0, "senderFirstName": "Alice", "senderLastName": "Smith", "senderDob": "1985-03-15", "senderIdType": "passport", "senderIdNumber": "AB1234567", "senderNationality": "TZ", "senderTelephoneNo": "+255712345678", "receiverFirstName": "John", "receiverLastName": "Doe", "receiverLipaNumber": "888880", "relationship": "Family", "payoutCountry": "KE", "purposeOfTransfer": "family_support", "sourceOfFunds": "salary", "businessReference": "TEST-PAYMENT-KE-TILL-001" }'
const response = await fetch( "https://api.test.wakapay.io/business/payout/payment", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ type: "till", senderCurrency: "USD", receiverCurrency: "KES", amount: 100.0, senderFirstName: "Alice", senderLastName: "Smith", senderDob: "1985-03-15", senderIdType: "passport", senderIdNumber: "AB1234567", senderNationality: "TZ", senderTelephoneNo: "+255712345678", receiverFirstName: "John", receiverLastName: "Doe", receiverLipaNumber: "888880", relationship: "Family", payoutCountry: "KE", purposeOfTransfer: "family_support", sourceOfFunds: "salary", businessReference: "TEST-PAYMENT-KE-TILL-001", }), }, ); const data = await response.json(); console.log(data);

Till Response (201 Created)

{ "businessReference": "TEST-PAYMENT-KE-TILL-001", "receiverAmount": 100, "receiverCurrency": "KES", "senderAmount": 0.7751937984496124, "senderCurrency": "USD", "status": "termination_success", "totalDebited": 0.7906976744186047, "wakapayReference": "3454311f-364b-11f1-8c14-0242ac120008" }

Example Request - Paybill Payment

curl -X POST https://api.test.wakapay.io/business/payout/payment \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "type": "paybill", "senderCurrency": "USD", "receiverCurrency": "KES", "amount": 100.0, "senderFirstName": "Alice", "senderLastName": "Smith", "senderDob": "1985-03-15", "senderIdType": "passport", "senderIdNumber": "AB1234567", "senderNationality": "TZ", "senderTelephoneNo": "+255712345678", "receiverFirstName": "John", "receiverLastName": "Doe", "receiverLipaNumber": "123456", "receiverAccount": "ABC123", "relationship": "Family", "payoutCountry": "KE", "purposeOfTransfer": "family_support", "sourceOfFunds": "salary", "businessReference": "TEST-PAYMENT-KE-PAYBILL-001" }'

Response - Paybill (201 Created)

{ "businessReference": "TEST-PAYMENT-KE-PAYBILL-001", "receiverAmount": 100, "receiverCurrency": "KES", "senderAmount": 0.7751937984496124, "senderCurrency": "USD", "status": "termination_success", "totalDebited": 0.7906976744186047, "wakapayReference": "3487eae9-364b-11f1-8c14-0242ac120008" }

Transaction Status Values

StatusDescription
termination_pendingTransaction initiated, awaiting completion
termination_successTransaction completed successfully
termination_failureTransaction failed

Purpose of Transfer - Allowed Values

All valid values for the purposeOfTransfer field:

  • family_support - Family support and remittances
  • education - Educational expenses
  • business_payment - Business-related payments
  • medical - Medical expenses
  • investment - Investment purposes
  • construction - Construction expenses
  • rent - Rent and housing
  • general - General purposes
  • fuel - Fuel and energy
  • repairs - Repairs and maintenance
  • gift - Gifts
  • personal_care - Personal care expenses
  • food_and_groceries - Food and groceries
  • transport - Transportation
  • travel - Travel expenses
  • shopping - Shopping
  • entertainment - Entertainment
  • donations - Charitable donations
  • other - Other purposes

Source of Funds - Allowed Values

All valid values for the sourceOfFunds field:

  • salary - Employment salary
  • savings - Personal savings
  • business_income - Business income
  • sale_of_assets - Sale of assets
  • investment_income - Investment returns
  • other - Other sources

TESTENV Test Credentials

Test Phone Numbers (Mobile Money)

CountryPhone NumberStatusDisplay Name
Kenya (KE)+254700000001Verified, payout successAmina Mjema
Tanzania (TZ)+255700000001Verified, no FX rate configuredAmina Mjema

Test Lipa Numbers (Till/Paybill)

CountryTypeNumberAccount RefStatus
Kenya (KE)Till888880-Verified, payout success
Kenya (KE)Paybill123456ABC123Verified, payout success
Tanzania (TZ)Till600000-Verified, no FX rate
Tanzania (TZ)Paybill700000ABC123Verified, no FX rate

Note: Only USD→KES FX rate is configured in TESTENV. TZS payouts will fail with “rate USD_TZS not configured” error.

Error Responses

400 - Missing Required Field

{ "code": 0, "error": "receiverPhone is required" }
{ "code": 0, "error": "receiverLipaNumber is required" }

400 - Invalid purposeOfTransfer

{ "code": 0, "error": "purposeOfTransfer must be one of: [family_support, education, business_payment, medical, investment, construction, rent, general, fuel, repairs, gift, personal_care, food_and_groceries, transport, travel, shopping, entertainment, donations, other]" }

400 - Invalid sourceOfFunds

{ "code": 0, "error": "sourceOfFunds must be one of: [salary, savings, business_income, sale_of_assets, investment_income, other]" }

404 - FX Rate Not Configured

{ "code": 0, "error": "rate USD_TZS not configured" }

409 - Duplicate Reference

{ "code": 0, "error": "duplicate reference" }

412 - Insufficient Balance

{ "code": 0, "error": "insufficient available balance" }

422 - Account Not Active

{ "code": 0, "error": "account not active" }

Note: Non-test phone numbers in TESTENV will return this error.

Use Cases

Send Mobile Money Transfer

async function sendMobileMoneyTransfer(phone, amount, currency, countryCode) { // Step 1: Verify recipient const verifyResponse = await fetch( "https://api.test.wakapay.io/business/verify-transfer", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ countryCode: countryCode, phoneNumber: phone, }), }, ); const verifyData = await verifyResponse.json(); if (!verifyData.verified) { throw new Error("Recipient not verified"); } console.log(`Sending to: ${verifyData.displayName}`); // Step 2: Check FX rate const rateResponse = await fetch( `https://api.test.wakapay.io/business/rate?from=USD&to=${currency}`, { headers: { Authorization: `Bearer ${token}` }, }, ); const rateData = await rateResponse.json(); console.log(`FX Rate: ${rateData.rate}`); // Step 3: Send transfer const transferResponse = await fetch( "https://api.test.wakapay.io/business/payout/transfer", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ senderCurrency: "USD", receiverCurrency: currency, amount: amount, senderFirstName: "Alice", senderLastName: "Smith", senderDob: "1985-03-15", senderIdType: "passport", senderIdNumber: "AB1234567", senderNationality: "TZ", senderTelephoneNo: "+255712345678", receiverFirstName: verifyData.displayName.split(" ")[0], receiverLastName: verifyData.displayName.split(" ")[1] || "User", receiverPhone: phone, relationship: "Family", payoutCountry: countryCode, purposeOfTransfer: "family_support", sourceOfFunds: "salary", businessReference: `TRF-${Date.now()}`, }), }, ); return await transferResponse.json(); } // Usage await sendMobileMoneyTransfer("+254700000001", 100, "KES", "KE");

Send Till Payment

async function sendTillPayment(tillNumber, amount, currency, countryCode) { // Step 1: Verify till number const verifyResponse = await fetch( "https://api.test.wakapay.io/business/verify-payment", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ countryCode: countryCode, type: "till", lipaNumber: tillNumber, }), }, ); const verifyData = await verifyResponse.json(); if (!verifyData.verified) { throw new Error("Till number not verified"); } console.log(`Paying to: ${verifyData.displayName}`); // Step 2: Send payment const paymentResponse = await fetch( "https://api.test.wakapay.io/business/payout/payment", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ type: "till", senderCurrency: "USD", receiverCurrency: currency, amount: amount, senderFirstName: "Alice", senderLastName: "Smith", senderDob: "1985-03-15", senderIdType: "passport", senderIdNumber: "AB1234567", senderNationality: "TZ", senderTelephoneNo: "+255712345678", receiverFirstName: "Merchant", receiverLastName: "Name", receiverLipaNumber: tillNumber, relationship: "Business", payoutCountry: countryCode, purposeOfTransfer: "business_payment", sourceOfFunds: "business_income", businessReference: `TILL-${Date.now()}`, }), }, ); return await paymentResponse.json(); } // Usage await sendTillPayment("888880", 100, "KES", "KE");

Best Practices

  1. Always use unique references - Prevents duplicate payments (409 error)
  2. Verify recipients first - Use verify-transfer or verify-payment endpoints
  3. Check FX rates - Ensure currency pair is configured before cross-currency payouts
  4. Display verification results - Show displayName to user for confirmation
  5. Handle errors gracefully - Implement proper error handling for all error codes
  6. Use TESTENV numbers for testing - Use provided test credentials in test environment
  7. Include receiverAccount for paybill - Required field for paybill payments

Supported Countries

  • KE - Kenya
  • TZ - Tanzania
  • UG - Uganda
  • ZA - South Africa
Last updated on