Errors
One error shape, six types, stable codes. How to handle each class of error.
ZyloPay uses HTTP status codes: 2xx succeeded, 4xx the request cannot succeed as sent, 5xx something failed on ZyloPay's side. Every error has the same body:
{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_reused",
"message": "This Idempotency-Key was already used with a different request.",
"request_id": "6f1c2a9e-3b7d-4c55-9a0e-2d4b8f1e7c30"
}
}| Field | Meaning |
|---|---|
type | The class of error. One of the six below. |
code | A stable, machine-readable code. Branch on this. |
message | A human-readable explanation. It may change: never parse it. |
param | The parameter or header at fault, when there is one. |
request_id | The request's ID. Quote it to support. |
The full list of codes is in Error codes. New codes may be added without a new API version: handle unknown codes by their type and HTTP status.
Types and what to do
type | HTTP | What to do |
|---|---|---|
invalid_request_error | 400, 404, 409 | Fix the request. 409 codes mean the object is in the wrong state (for example payment_intent_unexpected_state, refund_in_progress). Retrying unchanged will fail again, except refund_in_progress. |
authentication_error | 401 | Check the key: missing, malformed, expired or revoked. Do not retry. |
permission_error | 403 | The key lacks a scope, the IP is not allowed, or the account cannot do this (kyb_required, account_disabled, account_restricted). Do not retry. |
idempotency_error | 400, 409 | Fix the key, or wait for the first request (idempotency_request_in_progress). See Idempotency. |
rate_limit_error | 429 | Wait Retry-After seconds, then retry. See Rate limits. |
api_error | 500, 503 | Retry with backoff and the same Idempotency-Key: after request_timeout it replays the real outcome once the work finished; after internal_error or service_unavailable it replays the same error, so check the object, then retry with a new key only if nothing happened. |
Declined payments are not errors
A customer's tap that is declined (frozen pass, expired approval, risk decision…) does not produce an HTTP error in your integration. The payment intent reports it in last_payment_error and a payment_intent.payment_failed event is sent. See payment decline codes.
Validation errors
A body or query that fails validation answers 400 with one of:
parameter_missing: a required parameter is absent (paramnames it);parameter_invalid: a value has the wrong type or is out of range;parameter_unknown: the endpoint does not accept this parameter. The API rejects unknown fields instead of ignoring them, so a typo never goes unnoticed.
Handling errors with the Node SDK
@zylopay/node throws one class per error type. Every error extends ZyloPay.errors.ZyloPayError and carries .type, .code, .param, .message, .requestId, .statusCode and .headers.
| Class | Raised for |
|---|---|
AuthenticationError | 401 authentication_error |
PermissionError | 403 permission_error |
InvalidRequestError | 400 and 409 invalid_request_error |
NotFoundError (subclass of InvalidRequestError) | 404 resource_missing |
IdempotencyError | idempotency_error |
RateLimitError | 429 rate_limit_error |
APIError | 5xx and unknown types |
ConnectionError | No response: network failure or timeout |
SignatureVerificationError | zp.webhooks.constructEvent rejected a webhook |
import ZyloPay from '@zylopay/node';
try {
await zp.refunds.create({ payment: 'pay_1042', amount: 1000000 }, { idempotencyKey: `refund-${returnId}` });
} catch (err) {
if (err instanceof ZyloPay.errors.NotFoundError) {
// resource_missing: wrong ID, or an object of the other mode
} else if (err instanceof ZyloPay.errors.InvalidRequestError) {
if (err.code === 'amount_too_large') return showRemainingAmount();
if (err.code === 'refund_in_progress') return retryLater();
} else if (err instanceof ZyloPay.errors.IdempotencyError) {
// idempotency_request_in_progress: the first attempt is still running
} else if (err instanceof ZyloPay.errors.PermissionError) {
alertOps(`ZyloPay refused: ${err.code}`, err.requestId); // insufficient_scope, ip_not_allowed, kyb_required...
} else if (err instanceof ZyloPay.errors.AuthenticationError) {
alertOps('ZyloPay key rejected', err.requestId);
} else if (err instanceof ZyloPay.errors.ConnectionError) {
// no response: retry later with the SAME idempotency key
}
throw err;
}The SDK retries network errors, 409 idempotency_request_in_progress, 429 and 5xx on its own before it throws (see Node SDK).