Skip to content
API DocsDocs

Error Handling Guide

HTTP errors, payment failures, retry strategies, and debugging patterns

5 min readUpdated Sep 9, 2025

Exirom APIs return two distinct categories of errors: HTTP-level errors (transport/auth issues) and payment-level failures (declines, fraud). Each requires different handling.


#HTTP Status Codes

StatusMeaningHow to Handle
200 OKRequest succeeded — even failed payments return 200Check transactionStatus in the body
400 Bad RequestInvalid or missing parametersFix payload: check required fields, data types, amount format
401 UnauthorizedMissing or expired tokenRe-authenticate via /api/v1/auth, then retry
403 ForbiddenValid token but insufficient permissionsVerify MID, contact Exirom support
404 Not FoundWrong endpoint path or unknown transactionIdCheck URL and IDs against the API Reference
409 ConflictDuplicate requestId for a different requestGenerate a new unique requestId
422 UnprocessableStructurally valid JSON but business rule violatedReview field constraints (e.g., amount precision, unsupported currency)
429 Too Many RequestsRate limit exceededBack off before retrying; reduce request rate
500 Internal Server ErrorServer-side errorRetry with exponential backoff; contact support if persistent
503 Service UnavailableTemporary outageRetry after delay; check API status page

Key rule: A 200 response does not mean the payment succeeded. Always inspect transactionStatus — a declined payment returns 200 with transactionStatus: "FAILED".


#Payment-Level Failures

When transactionStatus is FAILED, check declineCode for the reason:

CategoryExamplesAction
Retry-eligibleInsufficient funds, temporary issuer unavailablePrompt customer to retry or use another card
Fix requiredExpired card, wrong CVV, invalid card numberAsk customer to update card details
Do not retryCard blocked, fraud flagged, suspected fraudDo not retry — show a generic error and offer alternatives
Contact supportMerchant configuration issuesContact Exirom support

See the Decline Codes Reference for the complete table with customer-friendly messages.


#Retry Strategy for HTTP Errors

For payment decline retry logic (which declineCode values to retry, backoff schedules, idempotency), see Error Recovery Patterns.

#For HTTP 5xx errors (server-side)

Use exponential backoff with jitter:

async function withRetry(fn, maxAttempts = 4) {
  let delay = 1000;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxAttempts || err.status < 500) throw err;
      const jitter = Math.random() * 500;
      await new Promise(r => setTimeout(r, delay + jitter));
      delay *= 2; // 1s → 2s → 4s → 8s
    }
  }
}

#For payment retries

Always use the same requestId when retrying a failed payment — this prevents double charges if the first attempt actually succeeded. Only generate a new requestId when starting a brand-new payment for a different order. See Idempotency for the full pattern.

#For HTTP 429 errors (rate limits)

Slow down and retry after a delay. If a Retry-After header is present, wait that long before retrying. If it is not present, wait at least 60 seconds and resume below the documented rate limit. For production traffic that needs higher throughput, contact your assigned account manager to whitelist your server IPs. See Going to Production for production limits and Sandbox Test Data for sandbox limits.


#Debugging Checklist

When a request fails unexpectedly:

  1. Log everything — status code, full response body, traceId (in error responses), requestId
  2. Check transactionStatus — don't assume from HTTP status alone
  3. Check the declineCode — maps to a specific failure reason
  4. Verify environment — sandbox tokens won't work in production and vice versa
  5. Validate your payload — amount format (string, not number), currency (ISO 4217), required fields
  6. Check token expiry — tokens expire after 30 days; a 401 often means a stale token

#Error Response Format

Most Exirom error responses (4xx/5xx) include a traceId for support escalation. Rate-limit responses (429) may be returned before the request reaches the API and may not include a JSON body or traceId.

{
  "httpStatus": 401,
  "internalCode": "auth-4003",
  "errorMessage": "JWT token expiration error",
  "traceId": "b1abae42b85ce937dcc1db984d5fcc7a",
  "subErrors": []
}

Always include the traceId when it is present. For rate-limit responses without a traceId, include the timestamp, endpoint, HTTP status, and your source IP.


Was this helpful?