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 Code | Meaning | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully (payouts) |
| 400 | Bad Request | Invalid request parameters, validation error |
| 401 | Unauthorized | Authentication failed or token expired |
| 404 | Not Found | Resource does not exist, FX rate not configured |
| 409 | Conflict | Duplicate businessReference |
| 412 | Precondition Failed | Insufficient funds |
| 422 | Unprocessable Entity | Business 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
apiKeyfield - Missing
apiSecretfield - Empty
apiKeyorapiSecret
Invalid Payload
{
"code": 0,
"error": "invalid payload"
}Causes:
- Malformed JSON
- Wrong
Content-Typeheader (must beapplication/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
qrStringfield - 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 Status | Error Message | Cause | Solution |
|---|---|---|---|
| 400 | apiKey and apiSecret required | Missing credentials | Provide both apiKey and apiSecret |
| 400 | invalid payload | Malformed JSON or wrong Content-Type | Fix JSON syntax, use Content-Type: application/json |
| 400 | from and to are required | Missing FX rate parameters | Provide both from and to currencies |
| 400 | countryCode is required | Missing countryCode | Provide countryCode (KE, TZ, UG, ZA) |
| 400 | countryCode must be one of: KE, TZ, UG, ZA | Invalid countryCode | Use valid country code |
| 400 | type must be “paybill” or “till” | Invalid or missing type | Set type to “till” or “paybill” |
| 400 | lipaNumber is required | Missing lipaNumber | Provide till/paybill number |
| 400 | receiverPhone is required | Missing receiverPhone | Provide phone number for transfer |
| 400 | receiverLipaNumber is required | Missing receiverLipaNumber | Provide till/paybill number for payment |
| 400 | empty mobile number | Missing phoneNumber | Provide phoneNumber field |
| 400 | phoneNumber must use correct international prefix | Phone format doesn’t match country | Use correct format (e.g., +254… for KE) |
| 400 | purposeOfTransfer must be one of: […] | Invalid enum value | Use valid purposeOfTransfer value |
| 400 | sourceOfFunds must be one of: […] | Invalid enum value | Use valid sourceOfFunds value |
| 401 | invalid credentials | Wrong apiKey or apiSecret | Verify credentials |
| 401 | missing value in request header | Missing Authorization header | Add Authorization: Bearer YOUR_TOKEN |
| 401 | token is malformed | Invalid JWT | Request new access token |
| 404 | rate XXX_YYY not configured | FX rate not configured | Contact support or use different currency |
| 404 | transaction not found | Transaction doesn’t exist | Verify businessReference |
| 409 | duplicate reference | businessReference already used | Use unique reference |
| 412 | insufficient available balance | Not enough funds | Top up wallet or reduce amount |
| 422 | account not active | Non-test number in TESTENV | Use test numbers in TESTENV |
| 422 | unsupported or malformed QR payload | Invalid QR code | Use 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"}Related
- Authentication - Authentication errors and solutions
- API Reference - Endpoint-specific error responses