Skip to main content

Error Handling

The ZenPays REST API uses standard HTTP status codes and returns detailed error information in JSON format.

Error Response Format

All error responses follow this structure:

{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
// Additional error context (optional)
}
}
}

HTTP Status Codes

StatusDescription
200Success
201Resource created
400Bad Request - Invalid parameters
401Unauthorized - Invalid or missing authentication
403Forbidden - Insufficient permissions
404Not Found - Resource doesn't exist
409Conflict - Resource state conflict
422Unprocessable Entity - Validation error
429Too Many Requests - Rate limit exceeded
500Internal Server Error
502Bad Gateway - TSP provider error
503Service Unavailable

Error Codes

Authentication Errors (401)

CodeDescription
UNAUTHORIZEDMissing or invalid API key
INVALID_SIGNATUREHMAC signature verification failed
TIMESTAMP_EXPIREDRequest timestamp outside allowed window
API_KEY_REVOKEDAPI key has been revoked
API_KEY_EXPIREDAPI key has expired

Authorization Errors (403)

CodeDescription
FORBIDDENAccess denied
IP_NOT_WHITELISTEDRequest from unauthorized IP
INSUFFICIENT_PERMISSIONSAPI key lacks required scope
MERCHANT_SUSPENDEDMerchant account is suspended

Validation Errors (400/422)

CodeDescription
INVALID_REQUESTRequest format is invalid
VALIDATION_ERROROne or more fields failed validation
INVALID_AMOUNTAmount is invalid or out of range
INVALID_CURRENCYCurrency code not supported
INVALID_PAYMENT_METHODPayment method not available
MISSING_REQUIRED_FIELDRequired field is missing

Resource Errors (404/409)

CodeDescription
RESOURCE_NOT_FOUNDRequested resource doesn't exist
PAYMENT_INTENT_NOT_FOUNDPayment intent ID is invalid
TRANSACTION_NOT_FOUNDTransaction ID is invalid
CUSTOMER_NOT_FOUNDCustomer ID is invalid
REFUND_NOT_FOUNDRefund ID is invalid
DUPLICATE_REQUESTRequest with same idempotency key exists
STATE_CONFLICTResource is in conflicting state

Payment Errors

CodeDescription
PAYMENT_FAILEDPayment processing failed
PAYMENT_DECLINEDPayment was declined by provider
PAYMENT_EXPIREDPayment intent has expired
INSUFFICIENT_FUNDSCustomer has insufficient funds
CARD_DECLINEDCard was declined
FRAUD_DETECTEDSuspicious activity detected
LIMIT_EXCEEDEDTransaction limit exceeded

Channel Limit Errors (422)

These errors occur during payment confirmation when the amount violates min/max limits configured per payment method and currency.

CodeDescription
CHANNEL_LIMIT_MIN_EXCEEDEDAmount is below the minimum for the selected payment method
CHANNEL_LIMIT_MAX_EXCEEDEDAmount exceeds the maximum for the selected payment method
{
"success": false,
"error": {
"code": "CHANNEL_LIMIT_MIN_EXCEEDED",
"message": "The payment amount of 100 INR is below the minimum limit of 500 INR for UPI. Please use a different payment method or increase the amount.",
"type": "validation_error",
"details": {
"amount": 100,
"currency": "INR",
"paymentMethod": "UPI",
"minimumAmount": 500,
"maximumAmount": 49967
}
}
}

Refund Errors

CodeDescription
REFUND_FAILEDRefund processing failed
REFUND_NOT_ALLOWEDTransaction cannot be refunded
REFUND_AMOUNT_EXCEEDEDRefund amount exceeds original
REFUND_WINDOW_CLOSEDRefund period has expired

Rate Limit Errors (429)

CodeDescription
RATE_LIMIT_EXCEEDEDToo many requests

Response includes retry information:

{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 60 seconds",
"details": {
"limit": 1000,
"remaining": 0,
"resetAt": "2024-01-15T10:31:00.000Z"
}
}
}

Provider Errors (502)

CodeDescription
TSP_ERRORPayment provider returned an error
TSP_TIMEOUTPayment provider request timed out
TSP_UNAVAILABLEPayment provider is unavailable

Operational Errors

These codes describe higher-level "we couldn't even attempt this" failures — distinct from numeric transaction-state codes (bank decline, insufficient funds, etc.). They surface in error.code of the standard error envelope and the checkout SDK's payment-failed socket event.

CodeHTTPCustomer-facing messageWhat it meansSuggested action
NO_TSP_AVAILABLE503"We can't process this payment right now. Please try again in a few minutes, or contact support if the issue persists."No active payment routes are configured to fulfill the request.Retry after a short delay; escalate to support if persistent.
UNSUPPORTED_REGION400"This payment route isn't available in your region right now. Please try a different method or contact support."TSPs exist but none support the customer's country.Offer an alternate method or surface a region-specific support flow.
UNSUPPORTED_CURRENCY_PAIR400"We couldn't set up this payment with our provider. Try another currency or contact support."The provider rejected the source/destination currency combination.Suggest a supported currency.
PROVIDER_VALIDATION_ERROR400"We hit a problem setting up your payment. Please double-check your details and try again, or contact support."Upstream provider returned a 4xx the platform couldn't normalize further.Inspect details.providerResponse server-side; ask the customer to retry.
PROVIDER_UNAVAILABLE502"Our payment partner is having trouble right now. Please try again in a few minutes."Upstream provider returned 5xx or timed out.Retry with backoff; consider failover routing.
PAYMENT_STUCK200"This is taking longer than usual. We're still trying — feel free to wait, or come back via your dashboard."The intent has been in processing past the heartbeat threshold (~90s) with no transitions. Advisory only — not a terminal failure.Show a friendlier "still working" UI; keep the subscription alive.
PAYMENT_RETRIES_EXHAUSTED400"We tried several times but couldn't complete this payment. Please try a different payment method or contact support."Retry budget exhausted; intent moved to canceled.Offer a different method; investigate the last TSP failure for the underlying cause.
TSP_PAYMENT_FAILED400"Your payment couldn't be completed. Please try a different payment method or contact your bank."TSP / webhook reported a terminal payment failure.Surface to the customer; inspect details.providerResponse for upstream reason.
CRYPTO_DEPOSIT_FAILED400"We couldn't confirm your crypto deposit. If you've already sent funds, contact support with the transaction hash."Crypto deposit reported failure (chain reorg, address mismatch, amount divergence).Capture the tx hash and route to support.

These codes also appear as code on the WebSocket payment-failed event (see Payment Webhooks).

Validation Error Details

Validation errors include field-specific details:

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"fields": [
{
"field": "amount",
"message": "Amount must be greater than 0",
"code": "min"
},
{
"field": "currency",
"message": "Currency must be a valid ISO 4217 code",
"code": "invalid"
}
]
}
}
}

Error Handling Examples

async function createPayment(amount, currency) {
try {
const response = await fetch('https://api.zenpayz.com/api/v1/payment-intents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json',
},
body: JSON.stringify({ amount, currency }),
});

const data = await response.json();

if (!response.ok) {
// Handle specific error codes
switch (data.error?.code) {
case 'RATE_LIMIT_EXCEEDED':
const retryAfter = data.error.details?.resetAt;
console.log(`Rate limited. Retry after: ${retryAfter}`);
break;
case 'VALIDATION_ERROR':
data.error.details?.fields?.forEach(field => {
console.log(`${field.field}: ${field.message}`);
});
break;
case 'UNAUTHORIZED':
case 'INVALID_SIGNATURE':
console.log('Authentication failed. Check your API key and signature.');
break;
default:
console.log(`Error: ${data.error?.message}`);
}
return null;
}

return data.data;
} catch (error) {
console.error('Network error:', error.message);
return null;
}
}

Retry Strategy

For transient errors, implement exponential backoff:

async function requestWithRetry(fn, maxRetries = 3) {
let lastError;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
const code = error.code;

// Only retry on transient errors
const retriableErrors = [
'RATE_LIMIT_EXCEEDED',
'TSP_TIMEOUT',
'TSP_UNAVAILABLE',
];

if (!retriableErrors.includes(code)) {
throw error;
}

// Exponential backoff: 1s, 2s, 4s...
const delay = Math.pow(2, attempt) * 1000;
console.log(`Retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}

throw lastError;
}

Idempotency

To prevent duplicate operations, include an X-Idempotency-Key header:

curl -X POST https://api.zenpayz.com/api/v1/payment-intents \
-H "Authorization: Bearer zp_live_xxxxx" \
-H "X-Idempotency-Key: unique-request-id-123" \
-H "Content-Type: application/json" \
-d '{"amount": 1000, "currency": "USD"}'

If the same idempotency key is used within 24 hours, the API returns the original response instead of creating a duplicate.

Next Steps