All RohoPay errors follow a consistent JSON structure:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description of the error",
"reference": "RHP-2024-ABC123"
}
}
The reference field is included when the error is tied to a specific transaction.
HTTP Status Codes
| HTTP Status | Meaning |
|---|
200 | Request succeeded |
201 | Resource created |
400 | Bad request — validation error in your input |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — action not permitted (e.g., disburse in test mode) |
404 | Not found — transaction or resource doesn’t exist |
409 | Conflict — duplicate idempotency key with different params |
422 | Unprocessable — request is valid but logic fails (e.g., insufficient balance) |
429 | Too many requests — rate limit exceeded |
500 | Internal server error — contact support |
503 | Service unavailable — provider is temporarily unreachable |
Error Codes
Authentication Errors
| Code | Meaning |
|---|
UNAUTHORIZED | Missing or invalid API key |
API_KEY_INACTIVE | API key has been revoked or rotated |
ENVIRONMENT_MISMATCH | Using a test key with live params or vice versa |
Validation Errors
| Code | Meaning |
|---|
VALIDATION_ERROR | One or more fields are invalid (see message for details) |
MISSING_IDEMPOTENCY_KEY | Idempotency-Key header not provided |
INVALID_PHONE | Phone number format is incorrect |
INVALID_CURRENCY | Currency code not supported |
INVALID_AMOUNT | Amount is zero, negative, or below minimum |
INVALID_CARD | Card number, expiry, or CVV is invalid |
CARD_EXPIRED | Card expiry date is in the past |
Business Logic Errors
| Code | Meaning |
|---|
INSUFFICIENT_BALANCE | Wallet balance too low for this disbursement |
DISBURSE_TEST_BLOCKED | Disbursements are not allowed in test mode |
DAILY_LIMIT_EXCEEDED | Daily transaction volume limit reached |
TEST_QUOTA_EXCEEDED | Test-mode request quota for today exhausted |
Provider Errors
| Code | Meaning |
|---|
PROVIDER_ERROR | The payment provider returned an error |
PROVIDER_LINE_DOWN | Provider network is temporarily unavailable |
PROVIDER_TIMEOUT | Provider did not respond in time |
Idempotency Errors
| Code | Meaning |
|---|
IDEMPOTENCY_CONFLICT | Same key used with different request body |
DUPLICATE_REQUEST | Exact same request already processed (returns original response) |
Handling Errors in Code
interface RohoPayError {
code: string;
message: string;
reference?: string;
}
async function collectPayment(phone: string, amount: number) {
const res = await fetch("https://api.rohopay.com/api/v1/collect", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ROHOPAY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ phone, amount, currency: "UGX" }),
});
const body = await res.json();
if (!body.success) {
const err: RohoPayError = body.error;
switch (err.code) {
case "VALIDATION_ERROR":
throw new Error(`Invalid input: ${err.message}`);
case "INSUFFICIENT_BALANCE":
throw new Error("Your wallet balance is too low.");
case "PROVIDER_LINE_DOWN":
throw new Error("Payment network is temporarily unavailable. Try again shortly.");
case "RATE_LIMIT_EXCEEDED":
await new Promise(r => setTimeout(r, 2000));
return collectPayment(phone, amount); // retry
default:
throw new Error(`Payment failed: ${err.message}`);
}
}
return body.data;
}
Retry Strategy
| Error Code | Retry? | Wait |
|---|
VALIDATION_ERROR | No | Fix your request |
UNAUTHORIZED | No | Check API key |
PROVIDER_LINE_DOWN | Yes | 30s → 60s → 120s |
PROVIDER_TIMEOUT | Yes | 10s → 20s → 40s |
RATE_LIMIT_EXCEEDED | Yes | 2s → 4s → 8s |
INSUFFICIENT_BALANCE | No | Top up wallet first |
500 Internal Error | Yes | 5s → 15s → 60s |
Always use the same idempotency key when retrying the same transaction. Never generate a new key for a retry — that creates a duplicate charge.