Skip to main content

Error Codes

This reference documents every error code returned by the ItPay Protocol API. Understanding these codes — their HTTP status, meaning, and retry semantics — is essential for building robust payment integrations.


1. Error Response Format

All API errors follow a consistent JSON structure. Every error response includes a code, a message, and optionally a details object with additional context.

Standard Shape

{
"error": {
"code": "SERVICE_NOT_FOUND",
"message": "The requested service was not found. Verify the service_id and try again.",
"details": {
"service_id": "svc_invalid_999",
"reason": "no_manifest_registered"
}
},
"request_id": "req_abc123def456"
}
FieldTypeDescription
error.codestringMachine-readable error code. Use this for programmatic handling.
error.messagestringHuman-readable description of the error. Suitable for logging or display.
error.detailsobject(Optional) Additional context — field names, limits exceeded, resource IDs, etc.
request_idstringUnique identifier for the request. Include this when contacting support.

Example: Validation Error with Details

{
"error": {
"code": "AMOUNT_EXCEEDED",
"message": "The requested amount exceeds the maximum allowed for this service.",
"details": {
"requested": "5000.00",
"currency": "USD",
"max_allowed": "1000.00",
"service_id": "svc_weather_001"
}
},
"request_id": "req_xyz789ghi012"
}

2. Full Error Code Table

CodeHTTP StatusDescriptionRetryable
SERVICE_NOT_FOUND404The requested service_id does not have a registered manifest. Check that the service was discovered and installed before calling.No
INSTALL_REQUIRED403The caller has not installed the target service. The agent must complete the install flow before making payment requests.No
PAYMENT_REQUIRED402Payment is required to access the requested resource. Use the x402 flow — present the QR or auto-pay — then retry with a X-Payment-Proof header.Yes
PAYMENT_EXPIRED410The payment intent expired before settlement. The payer must initiate a new payment flow.No — create a new PaymentIntent
PAYMENT_CANCELLED400The payment was cancelled by the payer or the gateway before completion.No — create a new PaymentIntent
INSUFFICIENT_KYC403The agent or payer does not meet the KYC/KYB requirements for the requested amount or capability. Upgrade your KYC level to proceed.No
CHANNEL_UNAVAILABLE503The requested payment channel is temporarily unavailable (e.g., provider outage, maintenance). Switch to an alternative channel or retry after a delay.Yes — with backoff
AMOUNT_EXCEEDED400The requested amount exceeds the maximum per-transaction or daily limit for the service. Reduce the amount or wait for the limit window to reset.No
CURRENCY_UNSUPPORTED400The requested currency is not supported by the service or the selected payment channel. Check the service manifest's supported currencies.No
DUPLICATE_REQUEST409A request with the same idempotency key has already been processed. This is informational — the original response is returned.No (informational only)
SIGNATURE_INVALID401The request signature could not be verified. Check your API key, signing algorithm, and timestamp window.No — fix signature and retry
REFUND_NOT_ALLOWED400A refund was requested for a payment that does not support refunds, or the refund window has passed.No
SUBSCRIPTION_INACTIVE400The subscription is not in an active state. Check that it was successfully activated and has not expired or been cancelled.No

Channel-Specific Error Codes

Errors from the downstream payment channel (WeChat Pay, Alipay, etc.) are wrapped in ItPay error codes with the original channel error in details.channel_code.

CodeHTTP StatusDescriptionRetryable
CHANNEL_UNAVAILABLE503The payment channel is temporarily unavailable (outage, maintenance, rate limit). Switch to an alternative channel or retry with backoff.Yes — with backoff
CHANNEL_DOWNSTREAM_ERROR502The payment channel returned an unexpected error. See details.channel_code and details.channel_message for the original provider error.Yes
CHANNEL_MERCHANT_INVALID400The merchant's channel credentials (mchid, app_id, API certificate) are invalid or expired. Check the Channel Adapter configuration.No — fix credentials
CHANNEL_AUTH_EXPIRED401The channel authorization has expired (e.g., Alipay app_auth_token expired, WeChat API certificate expired). Re-authorize via the channel's ISV/服务商 flow.No — re-authorize
CHANNEL_QR_EXPIRED410The QR code's validity period ended before the user completed payment. Create a new PaymentIntent.No — create new intent
CHANNEL_REFUND_REJECTED400The channel rejected the refund. See details.channel_reason for provider-specific reason (e.g., WeChat: max 50 partial refunds exceeded, Alipay: refund window expired).No
CHANNEL_RATE_LIMITED429The downstream channel rate limited the request. Retry with the Retry-After header value.Yes — with retry-after

Example response:

{
"error": {
"code": "CHANNEL_MERCHANT_INVALID",
"message": "WeChat Pay merchant credentials are invalid. Verify mchid, API certificate, and APIv3 key.",
"details": {
"channel": "wechat",
"channel_code": "PARAM_ERROR",
"channel_message": "mchid and appid do not match",
"recommendation": "Check that the merchant's appid is correctly bound to mchid in the WeChat Pay merchant platform."
}
},
"request_id": "req_abc123def456"
}

3. HTTP Status Code Summary

Status CodeMeaningTypical Causes
400 — Bad RequestThe request is malformed or violates business rules.PAYMENT_CANCELLED, AMOUNT_EXCEEDED, CURRENCY_UNSUPPORTED, REFUND_NOT_ALLOWED, SUBSCRIPTION_INACTIVE
401 — UnauthorizedAuthentication failed.SIGNATURE_INVALID
402 — Payment RequiredPayment is needed before the resource can be served.PAYMENT_REQUIRED
403 — ForbiddenThe caller lacks permission or KYC level.INSTALL_REQUIRED, INSUFFICIENT_KYC
404 — Not FoundThe requested resource does not exist.SERVICE_NOT_FOUND
409 — ConflictThe request conflicts with current state.DUPLICATE_REQUEST
410 — GoneThe resource or payment intent is no longer available.PAYMENT_EXPIRED
429 — Too Many RequestsRate limit exceeded.Rate limiting (see rate limit headers for retry-after)
503 — Service UnavailableA downstream dependency is unavailable.CHANNEL_UNAVAILABLE

4. Error Handling Best Practices

Retry Strategies

Not all errors should be retried. The table above marks each error as retryable or non-retryable. Follow these guidelines:

Non-retryable errors (400, 401, 403, 404, 409, 410):

  • These indicate a client-side issue or terminal state.
  • Inspect the error.code and error.details to determine the root cause.
  • Fix the underlying issue (wrong service ID, expired intent, missing install, invalid signature) before retrying.

Retryable errors (402, 503):

  • PAYMENT_REQUIRED (402): Complete the payment flow (auto-pay or QR checkout), then retry the original request with the X-Payment-Proof header.
  • CHANNEL_UNAVAILABLE (503): Implement exponential backoff with jitter:
Retry 1: wait 1s
Retry 2: wait 2s
Retry 3: wait 4s
Retry 4: wait 8s
Max retries: 5

Always cap retries to prevent runaway loops. Use jitter to avoid thundering herd:

function backoff(attempt: number, baseMs: number = 1000): number {
const exponential = Math.min(baseMs * Math.pow(2, attempt), 30000);
const jitter = Math.random() * 1000;
return exponential + jitter;
}

HTTP 429 (Rate Limited):

  • Read the Retry-After header. If present, wait exactly that many seconds.
  • If no Retry-After header is present, use the same exponential backoff as 503.

Logging

Log every error response you receive. At minimum, capture:

  • request_id — enables support team to trace the full request lifecycle
  • error.code — for automated alerting and dashboards
  • error.message — for human-readable debugging
  • error.details — structured context for root cause analysis
  • HTTP status code and response headers
import logging

logger = logging.getLogger("itpay")

def handle_api_error(response, body):
logger.error(
"ItPay API error",
extra={
"request_id": body.get("request_id"),
"error_code": body.get("error", {}).get("code"),
"error_message": body.get("error", {}).get("message"),
"details": body.get("error", {}).get("details"),
"http_status": response.status,
},
)

Set up alerts on error codes that indicate operational issues:

  • P0 alert: CHANNEL_UNAVAILABLE (payment channel outage affecting all users)
  • P1 alert: SIGNATURE_INVALID spikes (possible credential issue or misconfiguration)
  • P2 alert: AMOUNT_EXCEEDED or CURRENCY_UNSUPPORTED (integration configuration drift)

Webhook Error Recovery

ItPay webhooks deliver asynchronous updates about payment intents, subscriptions, and refunds. If your webhook endpoint returns a non-2xx status, ItPay retries the delivery:

AttemptDelay After Previous
1st retry10 seconds
2nd retry1 minute
3rd retry10 minutes
4th retry1 hour
5th retry6 hours
6th retry24 hours

After the 6th retry failure, the webhook is permanently dropped. ItPay sends a webhook.dropped notification event via the API so you can audit missed deliveries.

Best practices for webhook recovery:

  1. Idempotent handlers — Your webhook endpoint must handle duplicate deliveries safely. Use the payment_intent_id or event_id as an idempotency key. ItPay guarantees at-most-once processing per unique event_id.

  2. Return 2xx quickly — Acknowledge receipt immediately and process the event asynchronously if needed. Return 200 OK within 5 seconds or ItPay marks the delivery as failed.

  3. Verify signatures — Always verify the X-ItPay-Signature header before acting on a webhook payload (see Security Model).

  4. Poll as fallback — For critical events, implement a periodic reconciliation poll using the List Payments or Get Payment Intent endpoints to catch any webhooks that were dropped.

// Webhook handler — idempotent, verified, async
app.post("/webhooks/itpay", async (req, res) => {
// 1. Verify signature
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
req.headers["x-itpay-signature"],
process.env.ITPAY_WEBHOOK_SECRET
);
if (!isValid) return res.status(401).send("Invalid signature");

const event = req.body;
const eventId = event.id;

// 2. Idempotency check
const alreadyProcessed = await redis.exists(`webhook:${eventId}`);
if (alreadyProcessed) return res.status(200).send("OK");

// 3. Acknowledge immediately
res.status(200).send("OK");

// 4. Process asynchronously
await processEvent(event);

// 5. Mark processed
await redis.set(`webhook:${eventId}`, "1", "EX", 86400);
});

  • x402 HTTP 402 Semantics — The payment flow that produces PAYMENT_REQUIRED errors
  • Security Model — Signature verification, webhook validation, and anti-replay protection
  • KYC/KYB — KYC level requirements that trigger INSUFFICIENT_KYC errors
  • Service Manifest — Service configuration that determines available currencies and limits