Skip to main content
PayHubDeveloper docs
简体中文

Error codes and HTTP status

HTTP status shows if a request ran and status gives the result, plus error structure, retryable, 30 error codes, errors by endpoint and a handling example.

Last updated 2026-10-20 · API contract v1.0.0

Summary

  1. The HTTP status tells you whether the request was executed. A 4xx means the request was rejected and had no business effect (a 409 means a related object already exists). A 2xx means it was processed.
  2. The business result is in the object's status. A 2xx doesn't mean the charge succeeded. A payment has succeeded only when status=succeeded, or when you receive a payment.succeeded event whose signature you've verified.
  3. A 5xx or a timeout means "outcome unknown". The request may already have been accepted. Retry with the original idempotency key, or query by merchant order ID, and never charge again with a new key (the exception is 503 temporarily_unavailable, where this request wasn't accepted).
What to do after an error
What to do after an error

HTTP status

HTTPMeaningWhat you should doPossible business effect?Retry with the original idempotency key?
200The response for a completed retrieval, confirmation or idempotent replayCheck the business statusYes, rely on statusNot needed
201A business object was createdThis doesn't mean the payment necessarily succeeded. Keep checking statusYesNot needed
202The platform accepted the request and the outcome is pendingSave the object ID, then query or wait for the webhookPossibly, the outcome is unknownYes (it returns the latest state of the same object), but prefer to wait for the webhook or query
400A parameter is missing or malformed, and so onFix the parameters. Don't charge again automaticallyNoFix the request and resubmit with a new key
401The key is invalid or no longer validCheck your credentialsNoYes, after you fix the credentials
403Not allowed by the key's scopes, the merchant's capabilities or the environmentCheck the permissions. Don't work around the restrictionNoResolve the permissions first
404The object doesn't exist or doesn't belong to the current merchant environmentCheck the ID and the environment of the key you used. The platform doesn't reveal whether another merchant's object existsNo—
409Idempotency conflict, order conflict, in-flight attempt or state conflictRetrieve the existing object (related_resource_id), then decide what to do nextA related object already existsDepends on the error code (yes for operation_in_progress, no for the others)
422A business validation failed, such as an unsupported currency, an amount mismatch or insufficient refund fundsFix the business input according to the error codeNoFix it and resubmit (with a new key)
429The request was rate limited and wasn't executedBack off and retry according to Retry-After, with the original idempotency keyNoYes, after waiting Retry-After seconds
5xxA platform or dependency errorThe request may have been accepted. Retry with the same key, or query by order ID. Never guess and charge again with a new keyUnknown (except 503 temporarily_unavailable, which wasn't accepted)Yes, or query by merchant order ID / refund ID
  • A network timeout, a dropped connection or no response at all is handled like a 5xx: the outcome is unknown.
  • 503 temporarily_unavailable is the only 5xx that clearly means "not accepted": the platform has paused new transactions on this channel, so this request wasn't executed and created no payment or charge. Retry later with the original idempotency key. The response doesn't give the internal reason. Treat every other 5xx as an unknown outcome.
  • A 5xx response may not be JSON (for example, HTML returned by a gateway). If you can't parse it, treat it as "outcome unknown" too.
  • Once the platform has accepted a payment, it never disguises a provider timeout as a 429. An accepted request returns 202 processing.

Error response structure

FieldTypeDescription
error.codestringA stable, machine-readable error code. Program against it, and tolerate unknown error codes
error.messagestringA sanitized, developer-facing description that contains no card data or keys. The wording may change, so don't parse it
error.fieldstring, nullableThe request field that caused the error (for example amount, browser_info.ip_address or Idempotency-Key). null when the error isn't tied to a field
error.related_resource_idstring, nullableThe ID of a related existing object, such as the conflicting payment pay_…. Retrieve it to decide what to do next
error.retryablebooleanWhether it's appropriate to retry the current request with the original idempotency key (see the next section)
request_idstringThe unique identifier of this request. Provide it when you troubleshoot or contact support
ErrorResponse example
{
  "error": {
    "code": "payment_in_progress",
    "message": "This order already has an unfinished payment attempt. Check the result of the original payment.",
    "field": null,
    "related_resource_id": "pay_demo_001",
    "retryable": false
  },
  "request_id": "req_demo_error"
}

Note: Which idempotency key to use after you fix a 400 / 422

A request rejected with 400, 401, 403 or 422 wasn't executed and doesn't consume the idempotency key. After you fix the parameters, resubmit with a new idempotency key. This rule applies only to 400 / 401 / 403 / 422, not to 409, 429 or 5xx.

What retryable means

  1. retryable=true only means "it's appropriate to resend the same request with the original idempotency key", as with rate_limited, operation_in_progress, internal_error and temporarily_unavailable.
  2. It doesn't authorize you to switch to a new idempotency key or a new merchant order ID, or to "create another payment". When the outcome is unknown, retrying with a new key can cause a duplicate charge.
  3. retryable=false doesn't mean "this payment failed". For example, payment_in_progress is false because the right move is to retrieve the existing payment, not to resend the request.
  4. When the provider clearly declines the charge, the API doesn't return an error. It returns the payment with status=failed and a failure object. See Payment failure reasons versus API errors.

Warning: When the outcome is unknown, don't charge again with a new idempotency key or order ID

A timeout, a 5xx response or a 202 response may already have charged the payer. Retry with the same idempotency key, or query by order ID. See How to check the status when the outcome is unknown.

Error code reference

Program against error.code, and tolerate error codes that are added in the future (handle them by HTTP status). You can link directly to any row with #code-<error_code>, for example #code-payment_in_progress.

Error codeHTTPMeaningCommon causesRecommended actionRetryable with the original key
invalid_requestExample: merchant_order_no has an invalid format400A parameter is missing or malformedIdempotency-Key is missing; the order ID contains invalid characters; payment_method_data and payment_method_id are submitted together; confirm=false carries card details; confirm=true has no return_url; a checkout session request sends both groups of identity parameters, or neither; a browser_info / billing_details field the provider requires is missing; the cursor, time format or limit is invalidFix the request according to error.field and resubmit it; don't charge again automaticallyNo
sensitive_data_rejectedExample: description appears to contain card numbers or other sensitive data400Free text appears to contain card numbers or other sensitive dataDigits that look like a card number, or similar data, appear in description or metadataRemove the sensitive content and resubmit; check why your system wrote card data into these fieldsNo
unauthenticatedExample: The API key is invalid or no longer valid401The API key is missing, invalid, revoked or expiredNo Authorization header was sent; the key was copied incompletely; the old key expired after a rotation; the key was revokedCheck your credentials: on the API Key page in the merchant portal, verify the key's status and environmentNo
forbiddenExample: The API key doesn't have the scope this operation requires403The key's scopes don't allow this operationCreating a refund with a read-only key; calling getEvent without the read scope for the event's business area; if the request's source IP isn't on the key's allowlist, the more specific ip_not_allowed is returnedUse a key that has the required scope; don't work around the restrictionNo
ip_not_allowedExample: The request's source IP isn't on this API key's allowlist403The request's source IP isn't on this API key's IP allowlistThe key has an IP allowlist, and the request came from a server IP that isn't registeredCheck the allowlist on the API Key page in the merchant portal, or send the request from a registered serverNo
capability_disabledExample: Direct card isn't enabled for this merchant403The corresponding capability isn't enabled for the merchant, or has been turned offDirect card isn't enabled; the payment or refund request capability has been turned off; the currency isn't enabledUse hosted checkout instead, or contact platform OperationsNo
merchant_not_activeExample: The merchant isn't currently accepting new transactions403The merchant isn't active, so new transactions aren't acceptedThe merchant has been suspended or closed; callbacks, queries and notifications for business that was already accepted aren't affectedContact platform OperationsNo
not_foundExample: The object doesn't exist or doesn't belong to the current merchant and environment404The object doesn't exist or doesn't belong to the current merchant and environmentAn incorrect ID; using a test key to look up a live object; looking up a platform exception refund (not returned by the open API)Check the ID and the environment of the key you used; the platform doesn't reveal whether another merchant's object existsNo
idempotency_conflictExample: The same Idempotency-Key has already been used for a request with different content409The same idempotency key was used for requests with different core business parametersThe key was reused but the amount, currency, order ID or similar parameters differThis is an integration bug: check your idempotency key generation logic; don't change the parameters and replay the original key; use a new key for a new operationNo
operation_in_progressExample: The same request is already being processed. Retry later with the original idempotency key409A request with the same idempotency key is still being processedRequests with the same key were sent concurrentlyRetry later with the same key, or retrieve related_resource_id; don't switch to a new keyYes
payment_in_progressExample: This order already has an unfinished payment attempt. Check the result of the original payment409This order already has an unfinished payment attemptCalling confirm again while an attempt is in flight (processing / requires_action), or resubmitting the same order with a new keyRetrieve the Payment that related_resource_id points to and continue according to its status (guide the payer through authentication or wait for the result); don't create a new orderNo
order_no_conflictExample: The merchant order ID already corresponds to a Payment409The merchant order ID already corresponds to a PaymentA different idempotency key is used with the same merchant order IDRetrieve related_resource_id; if you really need to collect payment again and the original payment is closed, use a new order IDNo
refund_no_conflictExample: The merchant refund ID has already been used409The merchant refund ID has already been usedThe same refund ID is submitted again with a different idempotency keyRetrieve the refund that related_resource_id points toNo
invalid_stateExample: The payment's current state doesn't allow this operation409The object's current state doesn't allow this operationCanceling a payment that is processing / succeeded; calling confirm on an order that has expired, been canceled or already succeeded; creating a checkout session for an order that can't be paidRetrieve the object's latest state, then decide what to doNo
attempt_limit_reachedExample: This order has reached the maximum number of attempts409This order has reached the maximum number of attemptsThe default maximum is 3 attempts, and the platform can lower itThis order can't be charged again; once you've confirmed the original payment is closed, use a new order IDNo
refund_amount_exceedsExample: The refund amount exceeds the principal available to refund422The refund amount exceeds the principal available to refundPrincipal available to refund = captured amount − successfully refunded principal − principal reserved by unfinished refunds − principal claimed by disputes; concurrent partial refunds exceed it in totalRetrieve the payment's captured_amount, refunded_amount and refund_reserved_amount, then request the refund again with the refundable amountNo
insufficient_refund_fundsExample: Available funds are insufficient to cover the refund principal and the refund fee; shortfall: USD 3.50422The merchant's available funds don't cover the refund principal and the refund feeThe funds haven't been settled yet or are already reserved for a payout; for example, with a payment of 100.00, a fee of 3.50 and only the net amount of 96.50 available, a full refund is short by 3.50message gives the shortfall, and no refund is created this time; after you top up offline and platform Finance records it, or after the platform approves using your reserve / held funds (the approval is valid for 7 days), resubmit with the same merchant refund ID and amount and a new idempotency key; the platform doesn't reduce the refund amount automatically or cover the shortfall for youNo
refund_not_allowedExample: This payment can't be refunded right now422This payment can't be refunded right nowThe payment didn't succeed; the refund window the provider allows has passed; the transaction is in dispute and the provider doesn't allow refundsFollow message; handle disputed transactions on the Disputes page in the merchant portalNo
currency_not_supportedExample: The currency isn't enabled or isn't in the available range422The currency isn't enabled or isn't in the available rangeThe currency isn't in the overlap between the merchant's configuration and the provider's capabilitiesUse an enabled currency or contact platform OperationsNo
amount_out_of_rangeExample: The amount isn't within the allowed range422The amount doesn't meet the limitsBelow the minimum or above the maximum amount per payment, or over the daily cumulative limit; the error doesn't return the specific thresholdsAdjust the amount or contact the platformNo
card_brand_not_supportedExample: The card brand isn't supported422The card brand isn't supportedThe card brand isn't in the overlap between the merchant's configuration and the provider's capabilitiesAsk the payer to use another cardNo
fee_exceeds_amountExample: The fee exceeds the transaction amount422The fee exceeds the transaction amountThe amount is too small, and the fee calculated from the rate exceeds itIncrease the amount or contact the platform to adjust the configurationNo
order_amount_mismatchExample: The order amount or currency doesn't match the existing payment422The amount or currency doesn't match when an existing order is reusedThe amount or currency of the same order ID changed when creating a checkout sessionThe amount can't be changed: close the original payment, then create a new one with a new order IDNo
payment_method_unusableExample: The payment credential can't be used for this payment422The payment credential can't be used for this paymentpayment_method_id doesn't belong to the current environment, provider account or this merchantGet a new payment credentialNo
url_not_allowedExample: return_url doesn't match a registered redirect URL rule422The redirect URL isn't allowedreturn_url / cancel_url doesn't match a registered redirect URL ruleRegister it on the redirect URL allowlist page in the merchant portal, then resubmitNo
risk_rejectedExample: The request didn't pass the risk check422The request was rejected by the platform's risk controlsThe request matched a basic rule such as a risk list; the specific rule and threshold aren't disclosedDon't try to work around it; contact the platform if you have questionsNo
rate_limitedExample: Too many requests. Retry later with the original idempotency key429Rate limited: this request wasn't executedA rate limit per merchant and per key; switching keys doesn't get around the merchant's overall limitWait for Retry-After seconds, then retry with the original idempotency keyYes
internal_errorExample: A platform processing error occurred. Retry with the original idempotency key or query by merchant order ID or refund ID500Platform error; the request may have been accepted—Retry with the original idempotency key, or query by merchant order ID / refund ID; never charge again with a new keyYes
upstream_unavailableExample: A dependent service is temporarily unavailable; the request may have been accepted502 / 503Dependency error; the request may have been acceptedA provider or platform dependency failed, and the platform can't confirm whether this request took effectSame as aboveYes
temporarily_unavailableExample: The service is temporarily unavailable. Retry later with the original idempotency key503Temporarily unavailable; this request wasn't acceptedThe platform has paused new transactions on this channel; the response doesn't give the internal reasonRetry later with the original idempotency key; this request created no Payment or charge; if it keeps happening, contact platform OperationsYes

Errors by endpoint

Every endpoint can return unauthenticated (401), forbidden (403), rate_limited (429), internal_error (500) and upstream_unavailable (502 / 503). Endpoints that take parameters or a request body can also return invalid_request (400). temporarily_unavailable (503) is returned only by the endpoints that start new transactions: createPayment, confirmPayment and createCheckoutSession. The table below lists only the error codes specific to each endpoint:

EndpointSpecific error codes
createPaymentCreate paymentinvalid_request, sensitive_data_rejected, capability_disabled, merchant_not_active, idempotency_conflict, operation_in_progress, payment_in_progress, order_no_conflict, currency_not_supported, amount_out_of_range, card_brand_not_supported, fee_exceeds_amount, payment_method_unusable, url_not_allowed, risk_rejected, temporarily_unavailable
listPaymentsList paymentsinvalid_request
getPaymentGet paymentnot_found
confirmPaymentConfirm paymentinvalid_request, capability_disabled, merchant_not_active, not_found, idempotency_conflict, operation_in_progress, payment_in_progress, invalid_state, attempt_limit_reached, card_brand_not_supported, payment_method_unusable, url_not_allowed, risk_rejected, temporarily_unavailable
cancelPaymentCancel paymentinvalid_request, not_found, operation_in_progress, invalid_state
createCheckoutSessionCreate hosted checkout sessioninvalid_request, sensitive_data_rejected, capability_disabled, merchant_not_active, not_found, idempotency_conflict, operation_in_progress, invalid_state, attempt_limit_reached, order_amount_mismatch, currency_not_supported, amount_out_of_range, url_not_allowed, temporarily_unavailable
getCheckoutSessionGet checkout sessionnot_found
createRefundCreate refundinvalid_request, sensitive_data_rejected, capability_disabled, not_found, idempotency_conflict, operation_in_progress, refund_no_conflict, refund_amount_exceeds, insufficient_refund_funds, refund_not_allowed
listRefundsList refundsinvalid_request
getRefundGet refundnot_found
listBalancesList balancesinvalid_request
listBalanceTransactionsList balance transactionsinvalid_request
listSettlementsList settlementsinvalid_request
getSettlementGet settlementnot_found
getPayoutGet payoutnot_found
getEventGet eventnot_found, forbidden

Payment failure reasons versus API errors

AspectAPI error (error.code)Payment failure reason (failure.code)
Where it appearsThe error object in a 4xx / 5xx responseThe failure object of the payment in a 2xx response or a webhook
MeaningThe request was rejected, conflicted or has an unknown outcomeThe request was accepted, and one payment attempt was clearly declined (for example, by the issuer)
Is there a payment?Usually no new object (on a 409, related_resource_id points to the existing object)Yes, with status=failed, and failure.attempt_id points to the failed attempt
Is there a webhook?NoYes, payment.failed (with the attempt ID and the current version)
How to retryFix the request. For 429 / 5xx, use the original idempotency keyThe payer uses another card, and you call confirm with a new idempotency key (within the validity period and attempt limit)
Can you show it to the payer?Usually not (it's an integration or system problem). You can show a generic messageYou can show the sanitized failure.message
Examplespayment_in_progress, idempotency_conflict, insufficient_refund_fundscard_declined

failed is not a terminal state. After you receive payment.failed, the payer can still retry and succeed, so process events by version. When 3DS authentication succeeds but the issuer declines the charge, the payment is failed with a failure object. That isn't an API error.

Failure reason codes

Payment failure reasons

These are mapped from the provider's result. Your client must tolerate unknown failure.code values and treat them as a failed payment, prompting the payer to use another card.

failure.codeCategoryMeaningCan the payer retry with another card?
card_declinedDeclined by issuerThe issuer declined the transaction (generic reason)Yes
expired_cardDeclined by issuerThe card has expiredYes
incorrect_card_dataDeclined by issuerThe card number, expiry date or CVC is incorrectYes (after checking and re-entering)
card_not_supportedDeclined by issuerThis card type isn't acceptedYes (with another card)
authentication_failedAuthentication failed3DS authentication failedYes
authentication_expiredAbandoned by payerAuthentication wasn't completed within the validity period, and it's confirmed that no charge was madeYes
risk_declinedDeclined by risk controlsDeclined by the platform's or the provider's risk controls; the specific rule isn't disclosedDepends
processing_errorTechnical errorProvider processing failed, and it's confirmed that no charge was madeYes
upstream_amount_mismatchTechnical errorThe amount or currency the provider actually charged doesn't match the order, and the platform has returned the amount debited to the cardholder's original payment method; this return doesn't appear in the refund endpoints and doesn't affect your balance or refundable amountDepends (yes while the order is within its validity period and attempt limit)

When the payer closes the authentication page, the platform doesn't infer a result, so no failure reason appears right away. A reason such as authentication_expired appears only after the provider confirms that no charge was made.

Refund failure reasons

These appear only when a refund is failed or rejected:

failure.codeMeaning
refund_rejectedThe refund was rejected in review
refund_failedThe provider clearly did not execute the refund

Handling example

The functions below classify the result of a call into "what to do next". Unknown error codes fall into the default branch and are handled by HTTP status.

Node.js
const API_BASE = 'https://api.payhub.example';

// Return "what to do next"; unknown error codes are handled by HTTP status
export async function callPayHub(method, path, { body, idempotencyKey } = {}) {
  let res;
  try {
    res = await fetch(`${API_BASE}${path}`, {
      method,
      headers: {
        Authorization: `Bearer ${process.env.PAYHUB_API_KEY}`,
        ...(body ? { 'Content-Type': 'application/json' } : {}),
        ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {})
      },
      body: body ? JSON.stringify(body) : undefined,
      signal: AbortSignal.timeout(20_000)
    });
  } catch (cause) {
    return { next: 'unknown', cause }; // Timeout or network error: outcome unknown, so retry with the same key or query by order ID
  }
  const data = await res.json().catch(() => null); // A 5xx response may not be JSON
  const requestId = data?.request_id;
  if (res.ok) return { next: 'check_status', httpStatus: res.status, data, requestId };
  if (res.status === 429) {
    const raw = res.headers.get('Retry-After');
    const after = raw === null ? NaN : Number(raw);
    return { next: 'retry_same_key', afterSeconds: Number.isFinite(after) ? after : 1, requestId };
  }
  if (res.status >= 500) {
    // 503 temporarily_unavailable: not accepted, so retry later with the original idempotency key; any other 5xx: outcome unknown
    if (data?.error?.code === 'temporarily_unavailable')
      return { next: 'retry_same_key_later', requestId };
    return { next: 'unknown', requestId };
  }
  const error = data?.error ?? {};
  switch (error.code) {
    case 'payment_in_progress':
    case 'order_no_conflict':
    case 'refund_no_conflict':
      return { next: 'query_existing', id: error.related_resource_id, requestId };
    case 'operation_in_progress':
      return { next: 'retry_same_key', afterSeconds: 2, requestId };
    case 'idempotency_conflict':
      return { next: 'fix_integration', requestId }; // Same key, different parameters: fix key generation and alert
    default:
      return {
        next: 'rejected',
        httpStatus: res.status,
        code: error.code,
        field: error.field,
        message: error.message,
        requestId
      };
  }
}

Contact support

  • First, look up the call in the merchant portal by request_id. The record shows the method, path, duration, HTTP status and a sanitized error summary: API call logs (opens the merchant portal in a new tab) (Requires Owner, Admin or Developer).
  • When you contact support, include the request_id, the time it happened (with time zone), the environment (test / live), the endpoint, and the merchant order ID or the platform object ID.
  • Never send an API key, a webhook signing secret, a full card number, a CVC, or an authentication or checkout link that contains a token.
Was this page helpful?