Core Concepts
Error Handling
Xtopay error format and how to handle failures gracefully.
Error response format
All errors return a consistent JSON body:
{
"error": {
"code": "insufficient_funds",
"message": "The customer's account does not have sufficient funds.",
"param": null,
"doc_url": "https://docs.xtopay.co/errors/insufficient_funds"
}
}| Field | Description |
|---|---|
code | Machine-readable error code — use this in your code |
message | Human-readable explanation |
param | The request parameter that caused the error, if applicable |
doc_url | Link to the relevant error documentation |
HTTP status codes
| Status | Meaning |
|---|---|
200 | Success |
201 | Resource created |
400 | Bad request — malformed JSON or invalid parameter |
401 | Unauthenticated — credentials missing or invalid |
403 | Forbidden — insufficient scope or wrong environment |
404 | Resource not found |
409 | Conflict — e.g. duplicate idempotency key with different body |
422 | Unprocessable — valid format but business logic error |
429 | Rate limited |
500 | Xtopay server error |
Common error codes
| Code | Status | Meaning |
|---|---|---|
invalid_credentials | 401 | Client ID or Secret is wrong |
insufficient_funds | 422 | Card or mobile money account has no balance |
card_declined | 422 | Card issuer declined the charge |
invalid_phone | 400 | Mobile money number is not valid |
payment_not_found | 404 | Payment ID doesn't exist in this environment |
already_refunded | 409 | Payment has already been fully refunded |
subscription_cancelled | 409 | Action not allowed on a cancelled subscription |
rate_limit_exceeded | 429 | Too many requests — back off and retry |
Handling errors in code
import Xtopay, { XtopayError } from "@xtopay/node";
try {
const payment = await xtopay.payments.create({ ... });
} catch (err) {
if (err instanceof XtopayError) {
switch (err.code) {
case "insufficient_funds":
return { error: "Please top up your account and try again." };
case "card_declined":
return { error: "Your card was declined. Please try a different payment method." };
case "rate_limit_exceeded":
// Exponential backoff
await delay(2 ** attempt * 1000);
break;
default:
// Log and surface a generic error
logger.error("Xtopay error", { code: err.code, message: err.message });
return { error: "Payment could not be processed. Please try again." };
}
}
throw err; // re-throw unexpected errors
}Rate limits
| Endpoint type | Limit |
|---|---|
| Write operations (POST, PATCH, DELETE) | 100 requests / 10 seconds |
| Read operations (GET) | 300 requests / 10 seconds |
| Webhook delivery retries | Not counted against your limit |
Rate limit headers are included in every response:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1716537610