Errors

Error Reference

Understanding error codes and how to handle them in your integration.

Error Response Format

All API errors return a consistent JSON structure:

{
  "success": false,
  "error": {
    "code": "INVALID_PHONE",
    "message": "Invalid phone number format",
    "details": "Phone number must be in format 254XXXXXXXXX"
  }
}

Validation Errors

When multiple fields fail validation, the response includes an errors array:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "errors": [
      { "field": "phoneNumber", "message": "Required field" },
      { "field": "amount", "message": "Must be a positive number" },
      { "field": "orderId", "message": "Must be between 1-50 characters" }
    ]
  }
}

HTTP Status Codes

CodeNameDescription
400Bad RequestThe request was malformed or missing required parameters.
401UnauthorizedInvalid or missing API key.
403ForbiddenAPI key does not have permission for this action.
404Not FoundThe requested resource does not exist.
409ConflictResource already exists (e.g., duplicate orderId).
422Unprocessable EntityValidation failed. Check the errors array.
429Too Many RequestsRate limit exceeded. Retry after the specified time.
500Internal Server ErrorSomething went wrong on our end. Contact support.
503Service UnavailableService temporarily unavailable. Retry later.

M-PESA Payment Errors

CodeMessageResolution
INVALID_PHONEInvalid phone number formatUse format 254XXXXXXXXX (12 digits)
INVALID_AMOUNTAmount must be greater than 0Provide a positive number
DUPLICATE_ORDEROrder ID already existsUse a unique orderId for each transaction
INSUFFICIENT_BALANCEAccount balance too lowCustomer needs to top up M-PESA
USER_CANCELLEDCustomer cancelled the paymentPrompt customer to retry
TIMEOUTPayment request timed outCustomer did not respond within 60 seconds
INVALID_PINWrong M-PESA PIN enteredCustomer entered incorrect PIN
SERVICE_UNAVAILABLEM-PESA service temporarily downRetry after a few minutes

Card Payment Errors

CodeMessageResolution
CARD_DECLINEDCard was declined by issuerCustomer should try another card
INSUFFICIENT_FUNDSNot enough funds on cardCustomer needs sufficient balance
EXPIRED_CARDCard has expiredCustomer should use a valid card
INVALID_CVVCVV verification failedCustomer should re-enter CVV
INVALID_EXPIRYInvalid expiry dateUse MM/YY format with future date
3DS_FAILED3DS verification failedCustomer did not complete verification
FRAUD_SUSPECTEDTransaction flagged as suspiciousContact support for review
CARD_NOT_SUPPORTEDCard type not supportedUse Visa, Mastercard, or Amex

Payout Errors

CodeMessageResolution
INVALID_ACCOUNTBank account not foundVerify account number and bank code
INVALID_BANK_CODEBank code not recognizedUse valid bank code (e.g., KCB, EQUITY)
PAYOUT_LIMIT_EXCEEDEDAmount exceeds payout limitSplit into smaller payouts
INSUFFICIENT_FLOATNot enough float balanceTop up your SerixPay wallet
RECIPIENT_BLOCKEDRecipient account blockedRecipient should contact their bank
DUPLICATE_REFERENCEReference already usedUse unique reference for each payout

Rate Limiting

Rate Limits

API requests are limited to prevent abuse. When exceeded, you'll receive a 429 response.

Per API Key

Limits applied per API key across all endpoints.

Test mode100 req/min
Live mode1000 req/min

Per Endpoint

Additional limits on specific endpoints.

init-payment60 req/min
payouts30 req/min

Rate Limit Headers

All responses include headers to help you track your usage:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1705312800

Error Handling Best Practices

Always check the success field

Every response includes a boolean success field. Always check this before processing.

if (!response.success) {
  console.error(response.error.code, response.error.message);
  // Handle error appropriately
}

Implement exponential backoff

For rate limits and temporary failures, retry with increasing delays.

async function retryRequest(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (err) {
      if (err.status === 429 || err.status >= 500) {
        await sleep(Math.pow(2, i) * 1000);
        continue;
      }
      throw err;
    }
  }
}

Log errors for debugging

Log the full error response including the request ID for support tickets.

console.error('Payment failed:', {
  code: error.code,
  message: error.message,
  requestId: response.headers['x-request-id'],
  timestamp: new Date().toISOString()
});

Need Help?

If you encounter an error you can't resolve, contact our support team with:

  • The full error response (code, message, details)
  • The request ID from response headers
  • Timestamp of when the error occurred
  • The request payload (with sensitive data redacted)
Contact Support