API errors are returned in the following format.
{
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "Error description",
"request_id": "req_abc123xyz789"
}
}
Error type (authentication_error, invalid_request_error, payment_error, api_error)
Request ID (use when contacting support)
Parameter that caused the error (for validation errors)
Detailed error information (for validation errors)
Authentication Errors
| Code | HTTP Status | Description |
|---|
unauthorized | 401 | Authorization header is invalid or missing |
merchant_not_active | 403 | Merchant account is deactivated |
Validation Errors
| Code | HTTP Status | Description |
|---|
validation_error | 400 | Invalid request parameters. Details are in the details field |
invalid_amount | 400 | Invalid amount |
amount_below_minimum | 400 | Amount is below minimum |
amount_above_maximum | 400 | Amount exceeds maximum |
unsupported_currency | 400 | Unsupported currency |
Payment Errors
| Code | HTTP Status | Description |
|---|
payment_failed | 400 | Payment processing failed |
invalid_status | 400 | Operation cannot be performed in current status |
invalid_state | 400 | Invalid transaction state |
invalid_transaction | 400 | Transaction is invalid |
not_supported | 400 | This operation is not supported |
capture_failed | 400 | Capture failed |
refund_failed | 400 | Refund failed |
card_limit_exceeded | 400 | Monthly card spending limit exceeded |
email_limit_exceeded | 400 | Monthly email spending limit exceeded |
recurring_not_supported | 400 | Connector does not support recurring payments |
Token Errors
| Code | HTTP Status | Description |
|---|
tokenization_failed | 400 | Failed to tokenize card data |
payment_token_expired | 400 | Token has expired. Create a new token. (30 minute limit) |
payment_token_already_used | 400 | Token has already been used for a payment |
Webhook Payment Error Codes
When a payment fails, the payment.failed webhook includes detailed error information in a unified format that is consistent across all payment providers.
Error Structure
{
"event": "payment.failed",
"error": {
"code": "card_declined",
"category": "soft_decline",
"message": "Your card was declined",
"recommended_action": "use_different_card"
}
}
Error Categories
| Category | Description | Recommended Action |
|---|
authentication | 3DS authentication required | Redirect customer to complete authentication |
soft_decline | Temporary failure | Retry or use a different card |
hard_decline | Permanent failure | Do not retry with the same card |
gateway_error | Payment gateway issue | Retry later |
validation | API request validation failure (no webhook fired) | Fix the request and retry |
Unified Error Codes
| Code | Category | Description | Recommended Action |
|---|
authentication_required | authentication | 3DS authentication required | contact_customer |
authentication_failed | soft_decline | 3DS authentication failed | retry |
authentication_timeout | gateway_error | 3DS authentication timed out | retry |
card_declined | soft_decline | Card was declined | use_different_card |
insufficient_funds | soft_decline | Insufficient funds | use_different_card |
expired_card | hard_decline | Card has expired | use_different_card |
invalid_card | hard_decline | Invalid card number | use_different_card |
invalid_cvc | soft_decline | Invalid security code | retry |
invalid_expiry | soft_decline | Invalid expiration date | retry |
fraud_detected | hard_decline | Suspected fraud | none |
stolen_card | hard_decline | Stolen card | none |
lost_card | hard_decline | Lost card | none |
processing_error | gateway_error | Processing error | retry |
gateway_timeout | gateway_error | Gateway timeout | retry |
gateway_unavailable | gateway_error | Gateway unavailable | retry |
payment_failed | soft_decline | Payment failed (generic) | use_different_card |
three_ds_required | authentication | 3DS authentication required but not supported for this card | use_different_card |
refund_failed | gateway_error | Refund operation failed | contact_customer |
void_failed | gateway_error | Void operation failed | contact_customer |
status_check_failed | gateway_error | Unable to check payment status | retry |
unknown_error | soft_decline | Unknown error | use_different_card |
Validation errors (API request failures — visible in 400 response, no webhook fired):
| Code | Description |
|---|
unsupported_currency | Currency not supported by this payment method |
invalid_amount | Amount is not a valid positive integer |
invalid_parameter_combination | Request parameters have an invalid combination |
payment_method_not_found | Saved payment method not found |
customer_not_found | Customer not found |
missing_card | Card data is required but missing |
not_supported | Operation not supported by this connector |
Handling Example
// Webhook handler
app.post('/webhooks/zafapay', (req, res) => {
const { event, error } = req.body;
if (event === 'payment.failed' && error) {
switch (error.category) {
case 'authentication':
// Send email to customer with authentication link
notifyCustomerForAuth(error.message);
break;
case 'soft_decline':
// Can retry or ask for different card
if (error.recommended_action === 'retry') {
scheduleRetry();
} else {
requestNewCard();
}
break;
case 'hard_decline':
// Do not retry, notify customer
notifyCustomerCardInvalid(error.message);
break;
case 'gateway_error':
// Temporary issue, schedule retry
scheduleRetryWithBackoff();
break;
}
}
res.json({ received: true });
});
Error details from the original payment provider (PSP-specific codes) are not exposed in API responses or webhooks. Use the unified code and category fields to determine the appropriate action.
Refund Errors
| Code | HTTP Status | Description |
|---|
invalid_amount | 400 | Refund amount exceeds payment amount |
invalid_status | 400 | Not in refundable status (only completed can be refunded) |
Resource Errors
| Code | HTTP Status | Description |
|---|
not_found | 404 | Specified resource not found |
flow_not_found | 404 | Payment flow not found |
no_default_flow | 404 | Default payment flow not configured |
flow_not_active | 400 | Payment flow is disabled |
connector_config_not_found | 404 | Connector configuration not found |
Idempotency Errors
| Code | HTTP Status | Description |
|---|
idempotency_key_mismatch | 400 | Different request sent with same idempotency key |
idempotency_key_in_use | 409 | Idempotency key is currently being processed |
System Errors
| Code | HTTP Status | Description |
|---|
internal_error | 500 | Internal error occurred. Please contact support |
Error Handling Best Practices
Check HTTP Status Code
4xx: Client error (request needs modification)
5xx: Server error (retriable)
Branch Logic by Error Code
Use error.code to implement error-type-specific handling
Retry Strategy
For 5xx errors or idempotency_key_in_use, retry with exponential backoff
Sample Code
async function createPayment(data) {
const response = await fetch('https://api.sandbox.zafapay.com/v1/payments', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': generateIdempotencyKey()
},
body: JSON.stringify(data)
});
if (!response.ok) {
const { error } = await response.json();
// Log request_id for support inquiries
console.error(`API Error [${error.request_id}]:`, error.code, error.message);
switch (error.type) {
case 'authentication_error':
// Token refresh required
throw new AuthError(error.message);
case 'invalid_request_error':
// Check request parameters
if (error.details) {
console.error('Validation errors:', error.details);
}
throw new ValidationError(error.message);
case 'payment_error':
// Payment failed (try another payment method)
throw new PaymentError(error.message);
case 'api_error':
// Retriable
throw new RetryableError(error.message);
default:
throw new Error(error.message);
}
}
return response.json();
}