Skip to main content
The Orafi API uses conventional HTTP status codes and returns a consistent JSON error body so you can handle failures programmatically.

Error response format

Every error response follows this structure:
{
  "success": false,
  "message": "A human-readable explanation of the error."
}

HTTP status codes

Client errors (4xx)

CodeMeaningWhat to do
400Bad Request — Invalid parameters or malformed body.Check the request payload against the docs.
401Unauthorized — Missing or invalid API key.Verify the x-api-key header.
403Forbidden — Your account lacks permission.Complete onboarding or check mode.
404Not Found — The resource doesn’t exist.Verify the ID or path.
429Too Many Requests — Rate limit exceeded.Back off and retry after the Retry-After header.

Server errors (5xx)

CodeMeaningWhat to do
500Internal Server Error — Unexpected failure on our side.Retry with exponential backoff.
502Bad Gateway — An upstream dependency failed.Retry after a short delay.
503Service Unavailable — Temporary maintenance.Retry after a short delay.
Never retry 4xx errors automatically (except 429). Fix the request first.

Common failure scenarios

Payment failures

ScenarioCauseResolution
Insufficient balancePayout or refund exceeds available funds.Check balances before initiating.
Network congestionOn-chain confirmation delayed.Wait and poll or rely on webhooks.
Expired paymentCustomer didn’t pay within the time window.Create a new payment.
Invalid addressDeposit 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.