Skip to Content
DocumentationError reference

Error Handling

Learn how to handle errors effectively when integrating with the Wakapay API.

Error Response Format

Wakapay API uses two error response formats:

Format 1 - Standard Error

{ "code": 0, "error": "error message here" }

Format 2 - Authorization Error

{ "message": "error message here" }

HTTP Status Codes

Wakapay uses standard HTTP status codes:

Status CodeMeaningDescription
200OKRequest succeeded
201CreatedResource created successfully (payouts)
400Bad RequestInvalid request parameters, validation error
401UnauthorizedAuthentication failed or token expired
404Not FoundResource does not exist, FX rate not configured
409ConflictDuplicate businessReference
412Precondition FailedInsufficient funds
422Unprocessable EntityBusiness logic error (account not active, malformed QR)

Authentication Errors (401)

Invalid Credentials

{ "code": 0, "error": "invalid credentials" }

Causes:

  • Wrong apiKey
  • Wrong apiSecret
  • Credentials have been revoked

Solution: Verify your API credentials are correct

Missing Authorization

{ "message": "missing value in request header" }

Cause: No Authorization: Bearer YOUR_TOKEN header in request

Solution: Include the Authorization header with your access token

Token Malformed

{ "message": "token is malformed: token contains an invalid number of segments" }

Causes:

  • Invalid JWT format
  • Expired token
  • Corrupted token

Solution: Request a new access token

Validation Errors (400)

Missing Required Credentials

{ "code": 0, "error": "apiKey and apiSecret required" }

Causes:

  • Missing apiKey field
  • Missing apiSecret field
  • Empty apiKey or apiSecret

Invalid Payload

{ "code": 0, "error": "invalid payload" }

Causes:

  • Malformed JSON
  • Wrong Content-Type header (must be application/json)

Missing Country Code

{ "code": 0, "error": "countryCode is required" }
{ "code": 0, "error": "countryCode must be one of: KE, TZ, UG, ZA" }

Solution: Provide valid countryCode: KE, TZ, UG, or ZA

Missing Phone Number

{ "code": 0, "error": "empty mobile number" }

Solution: Provide phoneNumber field

Invalid Phone Format

{ "code": 0, "error": "phoneNumber must use the correct international prefix for the given countryCode" }

Solution: Use correct international format (e.g., +254700000001 for Kenya)

Invalid Payment Type

{ "code": 0, "error": "type must be \"paybill\" or \"till\"" }

Solution: Set type to either “till” or “paybill”

Missing Lipa Number

{ "code": 0, "error": "lipaNumber is required" }

Solution: Provide lipaNumber field for till/paybill verification

Missing Receiver Phone

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

Solution: Provide receiverPhone field for mobile money transfers

Missing Receiver Lipa Number

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

Solution: Provide receiverLipaNumber field for till/paybill payments

Invalid Purpose of Transfer

{ "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]" }

Valid values:

  • family_support, education, business_payment, medical, investment
  • construction, rent, general, fuel, repairs, gift, personal_care
  • food_and_groceries, transport, travel, shopping, entertainment
  • donations, other

Invalid Source of Funds

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

Valid values: salary, savings, business_income, sale_of_assets, investment_income, other

Missing FX Rate Parameters

{ "code": 0, "error": "from and to are required" }

Solution: Provide both from and to currency codes

Not Found Errors (404)

FX Rate Not Configured

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

Cause: Currency pair FX rate not configured in your account

Solution: Contact Wakapay support to configure the FX rate, or use a different currency pair

Note: In TESTENV, only USD→KES is configured (rate: 129)

Transaction Not Found

{ "code": 0, "error": "transaction not found" }

Cause: Transaction with given businessReference does not exist

Solution: Verify the transaction reference is correct

Conflict Errors (409)

Duplicate Reference

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

Cause: A transaction with this businessReference already exists

Solution: Use a unique reference for each transaction (e.g., include timestamp or UUID)

Precondition Failed (412)

Insufficient Balance

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

Cause: Not enough funds in wallet to complete transaction

Solution: Top up your wallet balance or reduce transaction amount

Unprocessable Entity (422)

Account Not Active

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

Cause: In TESTENV, non-test phone numbers return this error

Solution:

  • In TESTENV: Use test phone numbers (+254700000001, +255700000001)
  • In Production: Account activation may be required

Unsupported QR Payload

{ "code": 0, "error": "unsupported or malformed QR payload" }

Causes:

  • Invalid QR code format
  • Missing qrString field
  • QR code not EMV-compliant

Solution: Ensure QR code is valid EMV format from supported networks

Error Handling Best Practices

1. Check Response Status

const response = await fetch("https://api.test.wakapay.io/business/balance", { headers: { Authorization: "Bearer YOUR_ACCESS_TOKEN" }, }); if (!response.ok) { const errorData = await response.json(); console.error("Error " + response.status + ":", errorData); throw new Error(errorData.error || errorData.message); } const data = await response.json(); console.log("Success:", data);

2. Implement Retry Logic

async function makeRequestWithRetry(url, options, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch(url, options); const data = await response.json(); if (response.ok) { return data; } // Don't retry on client errors (4xx except 429) if ( response.status >= 400 && response.status < 500 && response.status !== 429 ) { throw new Error(data.error || data.message); } // Retry on server errors (5xx) and rate limits (429) if (attempt < maxRetries) { const delay = Math.min(1000 * Math.pow(2, attempt), 10000); await new Promise((resolve) => setTimeout(resolve, delay)); continue; } throw new Error(data.error || data.message); } catch (error) { if (attempt === maxRetries) { throw error; } } } }

3. Handle Specific Error Codes

async function sendPayout(payoutData) { try { const response = await fetch("/business/payout/transfer", { method: "POST", headers: { Authorization: "Bearer YOUR_ACCESS_TOKEN", "Content-Type": "application/json", }, body: JSON.stringify(payoutData), }); const data = await response.json(); if (!response.ok) { const errorMessage = data.error || data.message; switch (true) { case errorMessage.includes("insufficient available balance"): console.error("Insufficient balance - notify admin"); throw new Error("Unable to process: insufficient funds"); case errorMessage.includes("duplicate reference"): console.error("Duplicate reference detected"); throw new Error("Transaction already exists"); case errorMessage.includes("rate") && errorMessage.includes("not configured"): console.error("FX rate not configured"); throw new Error("Currency pair not supported"); case errorMessage.includes("account not active"): console.error("Account not active - use test numbers in TESTENV"); throw new Error("Recipient account not active"); case errorMessage === "missing value in request header": console.error("Authentication required"); throw new Error("Please authenticate"); default: throw new Error(errorMessage); } } return data; } catch (error) { console.error("Payout failed:", error); throw error; } }

4. Log Errors Properly

function logError(error, context = {}) { const errorLog = { timestamp: new Date().toISOString(), error: error.message, context: context, }; console.error("API Error:", JSON.stringify(errorLog, null, 2)); // Send to error tracking service in production if (process.env.NODE_ENV === "production") { // errorTracker.captureException(error, { extra: context }); } } try { await sendPayout(payoutData); } catch (error) { logError(error, { operation: "sendPayout", reference: payoutData.businessReference, amount: payoutData.amount, }); }

5. Provide User-Friendly Messages

function getUserFriendlyErrorMessage(error) { const errorMessage = error.message; const errorMap = { "insufficient available balance": "Unable to process payment due to insufficient funds. Please contact support.", "duplicate reference": "This transaction has already been processed.", "account not active": "Recipient account is not active. Please verify recipient details.", rate: "Currency conversion not available for this currency pair.", "transaction not found": "Transaction not found. Please check the reference.", "missing value in request header": "Authentication required. Please log in again.", "invalid credentials": "Invalid credentials. Please check your API keys.", "unsupported or malformed QR payload": "Invalid QR code. Please scan again.", "phoneNumber must use the correct international prefix": "Invalid phone number format. Use international format (e.g., +254700000001).", "countryCode must be one of": "Invalid country code. Supported: KE, TZ, UG, ZA.", }; for (const [key, message] of Object.entries(errorMap)) { if (errorMessage.includes(key)) { return message; } } return "An error occurred. Please try again or contact support."; }

Complete Error Reference Table

HTTP StatusError MessageCauseSolution
400apiKey and apiSecret requiredMissing credentialsProvide both apiKey and apiSecret
400invalid payloadMalformed JSON or wrong Content-TypeFix JSON syntax, use Content-Type: application/json
400from and to are requiredMissing FX rate parametersProvide both from and to currencies
400countryCode is requiredMissing countryCodeProvide countryCode (KE, TZ, UG, ZA)
400countryCode must be one of: KE, TZ, UG, ZAInvalid countryCodeUse valid country code
400type must be “paybill” or “till”Invalid or missing typeSet type to “till” or “paybill”
400lipaNumber is requiredMissing lipaNumberProvide till/paybill number
400receiverPhone is requiredMissing receiverPhoneProvide phone number for transfer
400receiverLipaNumber is requiredMissing receiverLipaNumberProvide till/paybill number for payment
400empty mobile numberMissing phoneNumberProvide phoneNumber field
400phoneNumber must use correct international prefixPhone format doesn’t match countryUse correct format (e.g., +254… for KE)
400purposeOfTransfer must be one of: […]Invalid enum valueUse valid purposeOfTransfer value
400sourceOfFunds must be one of: […]Invalid enum valueUse valid sourceOfFunds value
401invalid credentialsWrong apiKey or apiSecretVerify credentials
401missing value in request headerMissing Authorization headerAdd Authorization: Bearer YOUR_TOKEN
401token is malformedInvalid JWTRequest new access token
404rate XXX_YYY not configuredFX rate not configuredContact support or use different currency
404transaction not foundTransaction doesn’t existVerify businessReference
409duplicate referencebusinessReference already usedUse unique reference
412insufficient available balanceNot enough fundsTop up wallet or reduce amount
422account not activeNon-test number in TESTENVUse test numbers in TESTENV
422unsupported or malformed QR payloadInvalid QR codeUse valid EMV QR code

Testing Error Scenarios

Use these scenarios to test your error handling in TESTENV:

Test Invalid Credentials

curl -X POST https://api.test.wakapay.io/business/auth \ -H "Content-Type: application/json" \ -d '{"apiKey":"invalid","apiSecret":"wrong"}' # Returns: {"code":0,"error":"invalid credentials"}

Test Duplicate Reference

# Send same businessReference twice curl -X POST https://api.test.wakapay.io/business/payout/transfer \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{"businessReference":"SAME-REF-001",...}' # Second call returns: {"code":0,"error":"duplicate reference"}

Test Account Not Active

# Use non-test phone number curl -X POST https://api.test.wakapay.io/business/payout/transfer \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{"receiverPhone":"+254712345678",...}' # Returns: {"code":0,"error":"account not active"}

Test FX Rate Not Configured

curl "https://api.test.wakapay.io/business/rate?from=USD&to=TZS" \ -H "Authorization: Bearer TOKEN" # Returns: {"code":0,"error":"rate USD_TZS not configured"}
Last updated on