The Orafi API uses conventional HTTP status codes and returns a consistent JSON error body so you can handle failures programmatically.
Every error response follows this structure:
{
"success": false,
"message": "A human-readable explanation of the error."
}
HTTP status codes
Client errors (4xx)
| Code | Meaning | What to do |
|---|
400 | Bad Request — Invalid parameters or malformed body. | Check the request payload against the docs. |
401 | Unauthorized — Missing or invalid API key. | Verify the x-api-key header. |
403 | Forbidden — Your account lacks permission. | Complete onboarding or check mode. |
404 | Not Found — The resource doesn’t exist. | Verify the ID or path. |
429 | Too Many Requests — Rate limit exceeded. | Back off and retry after the Retry-After header. |
Server errors (5xx)
| Code | Meaning | What to do |
|---|
500 | Internal Server Error — Unexpected failure on our side. | Retry with exponential backoff. |
502 | Bad Gateway — An upstream dependency failed. | Retry after a short delay. |
503 | Service Unavailable — Temporary maintenance. | Retry after a short delay. |
Never retry 4xx errors automatically (except 429). Fix the request first.
Common failure scenarios
Payment failures
| Scenario | Cause | Resolution |
|---|
| Insufficient balance | Payout or refund exceeds available funds. | Check balances before initiating. |
| Network congestion | On-chain confirmation delayed. | Wait and poll or rely on webhooks. |
| Expired payment | Customer didn’t pay within the time window. | Create a new payment. |
| Invalid address | Deposit address format incorrect. | Verify the address before submitting. |
Retry strategy
Idempotency via txRef
Every payment accepts a txRef (transaction reference) that acts as an idempotency key. Sending the same txRef twice returns the original payment — no duplicate charge is created.
Exponential backoff
For server errors (5xx) and rate limits (429), implement exponential backoff:
async function createPaymentWithRetry(payload, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const res = await fetch("https://api.orafi.app/transactions/payment/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.ORAFI_API_KEY,
},
body: JSON.stringify(payload),
});
if (res.ok) return await res.json();
// Don't retry client errors (except 429)
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
throw new Error(`Client error ${res.status}: ${(await res.json()).message}`);
}
if (attempt === maxRetries) {
throw new Error(`Max retries reached (${res.status})`);
}
await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
} catch (err) {
if (attempt === maxRetries) throw err;
await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
}
}
}
Webhook retry behavior
Orafi automatically retries failed webhook deliveries. To ensure reliable processing:
- Return a
2xx status within 10 seconds to acknowledge receipt.
- Handle duplicates — your handler should be idempotent. Use the
transactionId to deduplicate.
- Check delivery history — Use the Webhook Deliveries endpoint to inspect attempt counts and response statuses.