Skip to main content

Error codes

CROSS Pay errors fall into two categories: SDK/runtime errors and REST API errors. When possible, the browser SDK surfaces structured errors in the following shape.

{
type: 'validation_error',
code: 'INVALID_REDIRECT_URL',
message: 'redirect URL is not allowed',
retryable: false,
requestId: 'req_...',
checkoutId: 'cs_...'
}

SDK error types

typeDescriptionDefault handling
network_errorCDN/runtime/API network failureShow a retry button when retryable
validation_errorValidation failure on request values, URLs, currency/countryPrompt the user to correct input
payment_errorPayment declined, expired, cancelled, or on-chain failurePrompt to change payment method or start a new session
authentication_errorAPI key/session authentication failureCheck server configuration
rate_limit_errorRequest quota exceededApply Retry-After or backoff
server_errorCROSS Pay or provider server errorRetry after a short delay
sdk_errorSDK load or widget lifecycle errorCheck integration code and CSP

Network / SDK load

coderetryableDescription
NETWORK_TIMEOUTNetwork request did not complete within the timeout
NETWORK_OFFLINEBrowser is offline
SDK_NOT_LOADEDRuntime script is not yet ready
WIDGET_LOAD_FAILEDWidget iframe/runtime failed to load
SSR_UNSUPPORTEDBrowser-only payment method invoked from a server environment
INVALID_SCRIPT_URLDisallowed cross-pay.js URL
HTTP_NOT_ALLOWEDHTTP used for a production script or redirect URL

At the SDK class level you can distinguish ScriptLoadFailedError, ScriptLoadTimeoutError, ScriptLoadAbortedError, NamespaceNotAvailableError, and CrossPaySDKError.

Validation

codeDescription
MISSING_PARAMRequired parameter missing
INVALID_FORMATFormat validation failed
INVALID_AMOUNTAmount is zero, negative, or outside the allowed range
INVALID_CURRENCYCurrency format is not valid
UNSUPPORTED_CURRENCYCurrency is not supported by CROSS Pay
UNSUPPORTED_COUNTRYCountry is unsupported or disabled
INVALID_RESPONSERuntime/API response shape did not match expectations
INVALID_REDIRECT_URLUnsafe redirect URL or not on the allowlist

Payment

coderetryableDescription
CHECKOUT_EXPIREDCheckout session TTL expired. A new POST /v1/payments is required
PAYMENT_DECLINEDThe provider declined the payment
PAYMENT_CANCELLEDThe user cancelled the payment or closed the window
USER_REJECTEDNormalized SDK code for user cancellation
PAYMENT_FAILEDGeneric payment failure
INSUFFICIENT_FUNDSInsufficient balance
POLLING_TIMEOUTStatus check did not complete within the timeout
POPUP_BLOCKEDBrowser blocked the popup
WALLET_INIT_FAILEDEmbedded wallet initialization failed
WRONG_CHAINWallet network differs from the payment chain
ONCHAIN_TX_FAILEDOn-chain transaction failed or reverted

Widget lifecycle

codeDescription
AMOUNT_NOT_SETSubmit attempted before widgets.setAmount() was called
WIDGET_NOT_MOUNTEDsubmit/update called while the payment method widget is not mounted
WIDGET_LOAD_FAILEDWidget iframe failed to load or bridge failed to initialize

Representative REST API codes

REST API error responses use the shape { code, message }, defined in internal/api/errors.go (cross-pay-api).

4xx (client-side errors)

codeHTTPDescription
BAD_REQUEST400Validation failure on request body/query/header
INVALID_AMOUNT400Invalid amount format or exceeds currency decimals
INVALID_CURRENCY400Invalid or unsupported currency code
UNAUTHORIZED401Missing or invalid merchant API key
FORBIDDEN403Attempted access to another merchant's resource
NOT_FOUND404Target resource not found
CHECKOUT_NOT_FOUND404Checkout id not found
PAYMENT_METHOD_NOT_FOUND404Selected payment method does not exist or is inactive
CONFLICT409Generic conflict
IDEMPOTENCY_CONFLICT409Same Idempotency-Key reused with a different request body
INVALID_CHECKOUT_STATE409SelectQuote called on a terminal-state checkout
INVALID_PAYMENT_STATE409Operation not allowed in the current payment state (e.g., cancel on PAID)
INVALID_TRANSITION409State transition violation. e.g., cancel on PROCESSING payment — to avoid races with the PG
CHECKOUT_EXPIRED410Checkout TTL expired — a new POST /v1/payments is required
SESSION_GONE410On-chain session ended or payment reached a terminal state
UNPROCESSABLE422Request is syntactically valid but semantically unprocessable
UNSUPPORTED_CURRENCY422Currency not supported for the payment method/country combination
UNSUPPORTED_COUNTRY422Unregistered or inactive country
PAYMENT_TOKEN_NOT_AVAILABLE422Token mapping is not enabled for the chain
PG_NOT_AVAILABLE422PG for the selected payment method is not available for this checkout
REFUND_EXCEEDS_REMAINING409Refund amount exceeds remaining refundable amount
REFUND_PROVIDER_UNAVAILABLE503PG refund channel is temporarily unavailable
RATE_LIMITED429Request quota exceeded

5xx (server-side errors)

codeHTTPDescription
INTERNAL_ERROR500Internal server error
SERVER_ERROR500(legacy alias) INTERNAL_ERROR
NOT_IMPLEMENTED501Route is exposed but not yet implemented
PG_UPSTREAM_ERROR502Upstream provider error
SERVICE_UNAVAILABLE503Temporary service unavailability
INVALID_TRANSITION vs INVALID_PAYMENT_STATE

Both codes are 409 but carry different intent.

  • INVALID_TRANSITION — state machine violation (e.g., attempting PROCESSING → CANCELLED). For the full lifecycle, see Webhooks Overview.
  • INVALID_PAYMENT_STATE — the operation itself does not fit the payment state (e.g., attempting a mutation rather than a simple lookup on PAID).

Merchants typically handle both codes uniformly as "not allowed in the current state" rather than branching on each one separately.

FDS deny

/v1/checkout/** responds with HTTP 200 even when FDS blocks the request. The body omits Payment/Quote objects and includes only the fds envelope.

{
"fds": {
"outcome": "DENY",
"code": "COUNTRY_BLOCKED",
"message": "Payment is blocked by risk policy",
"risk_level": "HIGH",
"risk_score": 92,
"rule": "country_allowlist"
}
}

The SDK and checkout UI must branch on fds.outcome, not the HTTP status. Treat unknown fds.code values as a generic block.