Skip to main content

Error Handling

Payment failures happen. Cards get declined. Networks time out. Prism gives you structured error information so you know exactly what went wrong — and what to do about it — regardless of which payment processor you are using.

How errors surface

Prism separates errors into two distinct categories based on how they reach you:

  • SDK exceptions (IntegrationError, ConnectorError, NetworkError) — thrown as exceptions. The call never returns a response object.
  • Payment errors — returned inside the response object as response.error. The call completes without throwing, but the connector returned HTTP 200 with a failure — a decline, insufficient funds, and so on.

You need to handle both.

SDK Exceptions

Integration Errors

These occur before Prism sends any request to the connector — during request validation, configuration checks, or request building. Because no request was sent, it is always safe to fix the issue and retry.

Fields:

FieldDescription
errorCodeSCREAMING_SNAKE_CASE string identifying the error
messageHuman-readable description
suggestedActionHow to fix it (optional)
docUrlLink to relevant documentation (optional)

Node.js

const { PaymentClient, IntegrationError } = require('hyperswitch-prism');

try {
const response = await client.authorize(request);
} catch (error) {
if (error instanceof IntegrationError) {
console.error(error.errorCode);
console.error(error.message);
if (error.suggestedAction) {
console.error(error.suggestedAction);
}
// Fix the request or configuration — do not retry as-is
}
}

Python

from orchestratorx_prism import PaymentClient, IntegrationError

try:
response = await client.authorize(request)
except IntegrationError as error:
print(error.error_code)
print(error.error_message)
if error.suggested_action:
print(error.suggested_action)
# Fix the request or configuration — do not retry as-is

Java

import payments.IntegrationError;

try {
PaymentServiceAuthorizeResponse response = client.authorize(request);
} catch (IntegrationError e) {
System.err.println(e.getErrorCode());
System.err.println(e.getMessage());
if (e.getSuggestedAction() != null) {
System.err.println(e.getSuggestedAction());
}
// Fix the request or configuration — do not retry as-is
}

PHP

use OrchestratorXPrism\PaymentClient;
use OrchestratorXPrism\Errors\IntegrationError;

try {
$response = $client->authorize($request);
} catch (IntegrationError $e) {
echo $e->getErrorCode() . "\n";
echo $e->getMessage() . "\n";
if ($e->getSuggestedAction()) {
echo $e->getSuggestedAction() . "\n";
}
// Fix the request or configuration — do not retry as-is
}

Connector Errors

These occur when the connector returns a 4xx or 5xx response, or when the response cannot be parsed — for example, if the connector changed its contract. Either way, the connector had a problem processing the request.

Fields:

FieldDescription
errorCodeString error code, e.g. "RESPONSE_DESERIALIZATION_FAILED"
messageHuman-readable description
httpStatusCodeHTTP status returned by the connector (optional)

Field access by language:

  • JS: error.errorCode, error.message, error.httpStatusCode
  • Python: error.error_code, error.error_message, error.http_status_code

Important: The payment may have been processed at the connector even when this error is thrown. Do not retry without first verifying payment status.

Node.js

const { PaymentClient, ConnectorError } = require('hyperswitch-prism');

try {
const response = await client.authorize(request);
} catch (error) {
if (error instanceof ConnectorError) {
console.error(error.errorCode);
console.error(error.message);
if (error.httpStatusCode) {
console.error(error.httpStatusCode);
}
// Payment may have been processed — investigate before retrying
throw error;
}
}

Python

from orchestratorx_prism import PaymentClient, ConnectorError

try:
response = await client.authorize(request)
except ConnectorError as error:
print(error.error_code)
print(error.error_message)
if error.http_status_code:
print(error.http_status_code)
# Payment may have been processed — investigate before retrying
raise

Java

import payments.ConnectorError;

try {
PaymentServiceAuthorizeResponse response = client.authorize(request);
} catch (ConnectorError e) {
System.err.println(e.getErrorCode());
System.err.println(e.getMessage());
if (e.getHttpStatusCode() != null) {
System.err.println(e.getHttpStatusCode());
}
// Payment may have been processed — investigate before retrying
throw e;
}

PHP

use OrchestratorXPrism\Errors\ConnectorError;

try {
$response = $client->authorize($request);
} catch (ConnectorError $e) {
echo $e->getErrorCode() . "\n";
echo $e->getMessage() . "\n";
if ($e->getHttpStatusCode()) {
echo $e->getHttpStatusCode() . "\n";
}
// Payment may have been processed — investigate before retrying
throw $e;
}

Network Errors

These occur during HTTP communication with the connector — after the request may have been sent. This is where retry logic gets dangerous in payment systems.

Fields:

FieldDescription
errorCodeString error code, e.g. "CONNECT_TIMEOUT_EXCEEDED" — use for logging and comparisons
messageHuman-readable description
statusCodeHTTP status code if available (optional)

Field access by language:

  • JS: error.errorCode, error.message, error.statusCode
  • Python: error.error_code, str(error), error.status_code

Retry safety: Most network errors happen after the request was already sent to the connector. Retrying without idempotency keys can cause double charges. Only retry CONNECT_TIMEOUT_EXCEEDED (connection never established) with confidence. For all others, verify payment status before retrying.

Node.js

const { PaymentClient, NetworkError } = require('hyperswitch-prism');

try {
const response = await client.authorize(request);
} catch (error) {
if (error instanceof NetworkError) {
console.error(error.errorCode);
console.error(error.message);
if (error.statusCode) {
console.error(error.statusCode);
}
// Do not retry blindly — verify payment status first
throw error;
}
}

Python

from orchestratorx_prism import PaymentClient, NetworkError

try:
response = await client.authorize(request)
except NetworkError as error:
print(error.error_code)
print(str(error))
if error.status_code:
print(error.status_code)
# Do not retry blindly — verify payment status first
raise

Java

import payments.NetworkError;

try {
PaymentServiceAuthorizeResponse response = client.authorize(request);
} catch (NetworkError e) {
System.err.println(e.getErrorCode());
System.err.println(e.getMessage());
if (e.getStatusCode() != null) {
System.err.println(e.getStatusCode());
}
// Do not retry blindly — verify payment status first
throw e;
}

PHP

use OrchestratorXPrism\Errors\NetworkError;

try {
$response = $client->authorize($request);
} catch (NetworkError $e) {
echo $e->getErrorCode() . "\n";
echo $e->getMessage() . "\n";
if ($e->getStatusCode()) {
echo $e->getStatusCode() . "\n";
}
// Do not retry blindly — verify payment status first
throw $e;
}

Payment Errors

Payment errors occur when the connector returns HTTP 200 but the payment did not go through — a card decline, insufficient funds, an expired card. These are not exceptions. The call returns normally and the error is inside response.error.

The error object has three layers:

  • unified_details — a standardized code and message that works the same across all connectors
  • connector_details — the raw code and message from the connector (e.g. Stripe, Adyen)
  • issuer_details — decline information from the card network or issuing bank, when available
{
"error": {
"unified_details": {
"code": "INSUFFICIENT_FUNDS",
"message": "Your card has insufficient funds.",
"description": "The payment was declined because the card does not have sufficient available credit or balance to complete the transaction.",
"user_guidance_message": "Please try a different payment method or contact your bank."
},
"issuer_details": {
"code": "VISA",
"message": "Decline",
"network_details": {
"advice_code": "01",
"decline_code": "51",
"error_message": "Insufficient funds"
}
},
"connector_details": {
"code": "card_declined",
"message": "Your card was declined.",
"reason": "insufficient_funds"
}
}
}

Use unified_details.code for your application logic. Use unified_details.user_guidance_message for messaging shown to end users — it is written for that purpose. The connector and issuer fields are useful for debugging and support.

Node.js

const { PaymentClient } = require('hyperswitch-prism');

const response = await client.authorize(request);

if (response.error) {
const unified = response.error.unifiedDetails;
const connector = response.error.connectorDetails;
const issuer = response.error.issuerDetails;

if (unified) {
console.error(unified.code); // e.g. "INSUFFICIENT_FUNDS"
console.error(unified.message);

if (unified.userGuidanceMessage) {
// Show this to the end user
showErrorToUser(unified.userGuidanceMessage);
}
}

if (connector) {
console.error(connector.code);
console.error(connector.reason);
}

if (issuer?.networkDetails) {
console.error(issuer.networkDetails.declineCode);
console.error(issuer.networkDetails.adviceCode);
}
} else {
console.log('Authorized:', response.connectorTransactionId);
}

Python

from orchestratorx_prism import PaymentClient

response = await client.authorize(request)

if response.error:
unified = response.error.unified_details
connector = response.error.connector_details
issuer = response.error.issuer_details

if unified:
print(unified.code) # e.g. "INSUFFICIENT_FUNDS"
print(unified.message)

if unified.user_guidance_message:
# Show this to the end user
show_error_to_user(unified.user_guidance_message)

if connector:
print(connector.code)
print(connector.reason)

if issuer and issuer.network_details:
print(issuer.network_details.decline_code)
print(issuer.network_details.advice_code)
else:
print(f'Authorized: {response.connector_transaction_id}')

Java

import payments.PaymentServiceAuthorizeResponse;

PaymentServiceAuthorizeResponse response = client.authorize(request);

if (response.hasError()) {
var error = response.getError();

if (error.hasUnifiedDetails()) {
var unified = error.getUnifiedDetails();
System.err.println(unified.getCode());
System.err.println(unified.getMessage());

if (unified.hasUserGuidanceMessage()) {
showErrorToUser(unified.getUserGuidanceMessage());
}
}

if (error.hasConnectorDetails()) {
var connector = error.getConnectorDetails();
System.err.println(connector.getCode());
System.err.println(connector.getReason());
}

if (error.hasIssuerDetails()) {
var issuer = error.getIssuerDetails();
if (issuer.hasNetworkDetails()) {
System.err.println(issuer.getNetworkDetails().getDeclineCode());
System.err.println(issuer.getNetworkDetails().getAdviceCode());
}
}
} else {
System.out.println("Authorized: " + response.getConnectorTransactionId());
}

PHP

use OrchestratorXPrism\PaymentClient;

$response = $client->authorize($request);

if ($response->getError()) {
$unified = $response->getError()->getUnifiedDetails();
$connector = $response->getError()->getConnectorDetails();
$issuer = $response->getError()->getIssuerDetails();

if ($unified) {
echo $unified->getCode() . "\n";
echo $unified->getMessage() . "\n";

if ($unified->getUserGuidanceMessage()) {
showErrorToUser($unified->getUserGuidanceMessage());
}
}

if ($connector) {
echo $connector->getCode() . "\n";
echo $connector->getReason() . "\n";
}

if ($issuer && $issuer->getNetworkDetails()) {
echo $issuer->getNetworkDetails()->getDeclineCode() . "\n";
echo $issuer->getNetworkDetails()->getAdviceCode() . "\n";
}
} else {
echo "Authorized: " . $response->getConnectorTransactionId() . "\n";
}

Complete example

Here is a complete authorize call with all error types handled:

Node.js

const {
PaymentClient,
IntegrationError,
ConnectorError,
NetworkError,
} = require('hyperswitch-prism');

async function authorizePayment(client, request) {
try {
const response = await client.authorize(request);

if (response.error) {
const unified = response.error.unifiedDetails;
console.error('Payment error:', unified?.code, unified?.message);
if (unified?.userGuidanceMessage) {
showErrorToUser(unified.userGuidanceMessage);
}
return null;
}

return response;
} catch (error) {
if (error instanceof IntegrationError) {
// Request never sent — fix the input or config
console.error('Integration error:', error.errorCode, error.message);
throw error;
}

if (error instanceof ConnectorError) {
// Payment may have been processed — investigate before retrying
console.error('Connector error:', error.errorCode, error.message);
throw error;
}

if (error instanceof NetworkError) {
// Request may have been sent — do not retry without verifying
console.error('Network error:', error.errorCode, error.message);
throw error;
}

throw error;
}
}

Python

from orchestratorx_prism import (
PaymentClient,
IntegrationError,
ConnectorError,
NetworkError,
)

async def authorize_payment(client, request):
try:
response = await client.authorize(request)

if response.error:
unified = response.error.unified_details
print(f'Payment error: {unified.code if unified else ""} {unified.message if unified else ""}')
if unified and unified.user_guidance_message:
show_error_to_user(unified.user_guidance_message)
return None

return response
except IntegrationError as error:
# Request never sent — fix the input or config
print(f'Integration error: {error.error_code} {error.error_message}')
raise
except ConnectorError as error:
# Payment may have been processed — investigate before retrying
print(f'Connector error: {error.error_code} {error.error_message}')
raise
except NetworkError as error:
# Request may have been sent — do not retry without verifying
print(f'Network error: {error.error_code} {str(error)}')
raise

Java

import payments.ConnectorError;
import payments.IntegrationError;
import payments.NetworkError;
import payments.PaymentClient;
import payments.PaymentServiceAuthorizeRequest;
import payments.PaymentServiceAuthorizeResponse;

public PaymentServiceAuthorizeResponse authorizePayment(PaymentClient client, PaymentServiceAuthorizeRequest request) throws Exception {
try {
PaymentServiceAuthorizeResponse response = client.authorize(request);

if (response.hasError()) {
var error = response.getError();
if (error.hasUnifiedDetails()) {
var unified = error.getUnifiedDetails();
System.err.println("Payment error: " + unified.getCode() + " " + unified.getMessage());
if (unified.hasUserGuidanceMessage()) {
showErrorToUser(unified.getUserGuidanceMessage());
}
}
return null;
}

return response;
} catch (IntegrationError e) {
// Request never sent — fix the input or config
System.err.println("Integration error: " + e.getErrorCode() + " " + e.getMessage());
throw e;
} catch (ConnectorError e) {
// Payment may have been processed — investigate before retrying
System.err.println("Connector error: " + e.getErrorCode() + " " + e.getMessage());
throw e;
} catch (NetworkError e) {
// Request may have been sent — do not retry without verifying
System.err.println("Network error: " + e.getErrorCode() + " " + e.getMessage());
throw e;
}
}

PHP

use OrchestratorXPrism\PaymentClient;
use OrchestratorXPrism\Errors\{IntegrationError, ConnectorError, NetworkError};

function authorizePayment(PaymentClient $client, $request) {
try {
$response = $client->authorize($request);

if ($response->getError()) {
$unified = $response->getError()->getUnifiedDetails();
echo "Payment error: " . $unified->getCode() . " " . $unified->getMessage() . "\n";
if ($unified->getUserGuidanceMessage()) {
showErrorToUser($unified->getUserGuidanceMessage());
}
return null;
}

return $response;
} catch (IntegrationError $e) {
// Request never sent — fix the input or config
echo "Integration error: " . $e->getErrorCode() . " " . $e->getErrorMessage() . "\n";
throw $e;
} catch (ConnectorError $e) {
// Payment may have been processed — investigate before retrying
echo "Connector error: " . $e->getErrorCode() . " " . $e->getErrorMessage() . "\n";
throw $e;
} catch (NetworkError $e) {
// Request may have been sent — do not retry without verifying
echo "Network error: " . $e->getErrorCode() . " " . $e->getMessage() . "\n";
throw $e;
}
}

Best Practices

Retry safety — the most important thing to get right:

Error typeRequest sent?Safe to retry?
IntegrationErrorNoYes, after fixing the issue
NetworkErrorCONNECT_TIMEOUT_EXCEEDEDNoYes, with idempotency key
NetworkError — all othersLikely yesOnly after verifying payment status
ConnectorErrorYesOnly after verifying payment status
Payment error (response.error)YesDepends on the decline code

Other practices:

  • Always check response.error after every call that returns successfully. A payment can fail at the processor without throwing an exception.
  • Use unified_details.code for your own logic — routing decisions, retry policies, alerting.
  • Use unified_details.user_guidance_message for messaging shown to end users. Do not expose connector_details or issuer_details to users.
  • Log errorCode, errorMessage, and HTTP status code on every error. These are the fields support will ask for first.
  • Track IntegrationError rates in production — a spike usually means a configuration or deployment issue.
  • Track ConnectorError rates — a spike usually means the connector is having problems or changed its API.

Error Code Reference

Error codes are always SCREAMING_SNAKE_CASE strings. Use them directly in comparisons:

// JavaScript
if (error.errorCode === 'MISSING_REQUIRED_FIELD') { ... }
# Python
if error.error_code == 'MISSING_REQUIRED_FIELD': ...

Integration Error Codes

These codes appear in IntegrationError. The request was never sent to the connector.

CodeDescription
FAILED_TO_OBTAIN_INTEGRATION_URLCannot determine the connector endpoint URL
REQUEST_ENCODING_FAILEDFailed to encode the connector request
HEADER_MAP_CONSTRUCTION_FAILEDCannot construct HTTP headers
BODY_SERIALIZATION_FAILEDCannot serialize the request body
URL_PARSING_FAILEDCannot parse the request URL
URL_ENCODING_FAILEDURL encoding of the request payload failed
MISSING_REQUIRED_FIELDA required field is missing in the request
MISSING_REQUIRED_FIELDSMultiple required fields are missing
FAILED_TO_OBTAIN_AUTH_TYPECannot determine the authentication type
INVALID_CONNECTOR_CONFIGInvalid connector configuration
NO_CONNECTOR_META_DATAConnector metadata not found
INVALID_DATA_FORMATData format validation failed
INVALID_WALLETInvalid wallet specified
INVALID_WALLET_TOKENFailed to parse wallet token (Apple Pay / Google Pay)
MISSING_PAYMENT_METHOD_TYPEPayment method type not specified
MISMATCHED_PAYMENT_DATAPayment method data does not match the payment method type
MANDATE_PAYMENT_DATA_MISMATCHFields do not match those used during mandate creation
MISSING_APPLE_PAY_TOKEN_DATAMissing Apple Pay tokenization data
NOT_IMPLEMENTEDFeature not yet implemented
NOT_SUPPORTEDFeature not supported by this connector
FLOW_NOT_SUPPORTEDPayment flow not supported by this connector
CAPTURE_METHOD_NOT_SUPPORTEDCapture method not supported
CURRENCY_NOT_SUPPORTEDCurrency not configured for this connector
AMOUNT_CONVERSION_FAILEDFailed to convert amount to the required format
MISSING_CONNECTOR_TRANSACTION_I_DConnector transaction ID not found
MISSING_CONNECTOR_REFUND_I_DConnector refund ID not found
MISSING_CONNECTOR_MANDATE_I_DConnector mandate ID not found
MISSING_CONNECTOR_MANDATE_METADATAConnector mandate metadata not found
MISSING_CONNECTOR_RELATED_TRANSACTION_I_DRequired related transaction ID not found
MAX_FIELD_LENGTH_VIOLATEDField exceeds maximum length for this connector
SOURCE_VERIFICATION_FAILEDFailed to verify request source (signature, webhook, etc.)
CONFIGURATION_ERRORGeneral configuration validation error

Note on _I_D suffix: Error codes for variants ending in ID (e.g. MissingConnectorTransactionID) serialize as ..._I_D due to how the code generator handles uppercase boundaries. Use the exact strings shown above in comparisons.

Connector Error Codes

These codes appear in ConnectorError. The connector returned a 4xx/5xx response or a response that could not be parsed. The payment may have been processed.

CodeDescription
RESPONSE_DESERIALIZATION_FAILEDCannot parse the connector response (invalid JSON/XML, unexpected format)
RESPONSE_HANDLING_FAILEDError occurred while processing the connector response
UNEXPECTED_RESPONSE_ERRORResponse structure does not match the expected schema
INTEGRITY_CHECK_FAILEDIntegrity check failed (e.g. amount or currency mismatch between request and response)

Network Error Codes

These codes appear in NetworkError. The request may or may not have been sent.

CodeDescriptionRetryable?
CONNECT_TIMEOUT_EXCEEDEDConnection timed out before being establishedYes — request was never sent
RESPONSE_TIMEOUT_EXCEEDEDConnector accepted the connection but did not respond in timeNo — request was likely sent
TOTAL_TIMEOUT_EXCEEDEDEntire request lifecycle exceeded the total timeoutNo — request may have been sent
NETWORK_FAILUREGeneric failure (DNS, connection refused, TLS handshake)Check whether failure occurred before or after sending
RESPONSE_DECODING_FAILEDFailed to read response body (dropped connection, corrupted data)No — response was received, payment processed
CLIENT_INITIALIZATION_FAILUREHTTP client failed to initializeNo — fix configuration
URL_PARSING_FAILEDRequest URL is malformed or uses an unsupported schemeNo — fix code
INVALID_PROXY_CONFIGURATIONProxy URL or configuration is invalidNo — fix configuration
INVALID_CA_CERTCA certificate (PEM/DER) is invalid or could not be loadedNo — fix configuration

Payment Error Codes

These codes appear in response.error.unified_details.code. They represent the standardized view of a connector-reported failure, mapped from connector-specific codes.

Prism maps each connector's error language to a single set of codes so your application handles them once regardless of processor.

Without Prism, you handle each connector separately:

if (connector === 'stripe') {
if (error.code === 'card_declined') { ... }
} else if (connector === 'adyen') {
if (error.resultCode === 'Refused') { ... }
} else if (connector === 'paypal') {
if (error.details[0].issue === 'INSTRUMENT_DECLINED') { ... }
}
// ...and so on for every connector

With Prism, you write it once:

if (response.error.unifiedDetails.code === 'PAYMENT_DECLINED') {
// Handles Stripe, Adyen, PayPal, and all others
}

Sample mapping across connectors:

Unified CodeDescriptionStripeAdyen
PAYMENT_DECLINEDGeneric declinecard_declinedRefused (refusalReasonCode: 2)
INSUFFICIENT_FUNDSCard has insufficient balancecard_declined + decline_code: insufficient_fundsNot enough balance (refusalReasonCode: 12)
EXPIRED_CARDCard is expiredexpired_cardExpired Card (refusalReasonCode: 6)
INCORRECT_CVVWrong security codeincorrect_cvcCVC Declined (refusalReasonCode: 24)
INVALID_CARD_NUMBERCard number is invalidincorrect_numberInvalid Card Number (refusalReasonCode: 8)
PROCESSING_ERRORGeneric processor errorprocessing_errorAcquirer Error (refusalReasonCode: 4)
RATE_LIMITEDToo many requestsHTTP 429Refusal code 46
INVALID_API_KEYAuthentication failedapi_key_expired / HTTP 401HTTP 401
VALIDATION_ERRORBad request formatHTTP 400HTTP 422