OBHPayments
↑↓ navigate↵ openSearches titles and topics
Documentation

Reference

Errors

Every API failure becomes one typed error. This page explains its fields, which codes the SDK generates itself, which come from the API, and how to recover without guessing.

One error class

All API and validation failures throw OBHPaymentsError. Nothing throws a string, and network failures are not raw TypeErrors. The original cause is preserved on error.cause when there is one. The browser guard is the one exception: it throws a plain Error, because the SDK is not meant to run there at all.

ts
import { isOBHPaymentsError } from "@obhco/payments" try {  await obh.startPayment(input)} catch (error) {  if (isOBHPaymentsError(error)) {    console.error(error.code, error.status, error.requestId)  } else {    // A bug in your code, not an API failure.    throw error  }}

isOBHPaymentsError() is an instanceof check with a type guard. Prefer it over inspecting error.name.

Error fields

PropertyTypeDescription
codeoptionalstringMachine-readable identifier. Either an SDK code or the API error code.
statusoptionalnumber | undefinedHTTP status for API errors. Undefined for local validation, network, and timeout errors.
requestIdoptionalstring | undefinedCorrelation id from the API envelope or the x-request-id response header.
detailsoptionalAPIErrorDetail[] | undefinedPer-field validation messages, each with an optional field and a message.
retryableoptionalbooleanTrue for timeouts, network errors, 429, and 5xx. False otherwise.
messageoptionalstringA human-readable message. API messages are safe to show to operators, not necessarily to customers.
causeoptionalunknown | undefinedThe underlying error for transport failures. Never shown to customers.

Details is for fields, not prose

details is populated from validation envelopes. Render it next to the relevant form field when field is present, and as a general message otherwise.

SDK-generated codes

These codes never come from the API. They are produced by the SDK before or around the call.

CodeWhenRetryableFix
INVALID_ARGUMENTConstructor or input validation failed — bad amount, URL, timeout, credential prefix, metadata, or idempotency key.NoCorrect the input. No request was sent.
NETWORK_ERRORfetch rejected before a response — DNS, TLS, connection reset, or offline.YesRetry with backoff, using the same idempotency key.
TIMEOUTThe SDK’s timeoutMs elapsed and the request was aborted.YesRetry or retrieve. The outcome is unknown, not failed.
INVALID_RESPONSEThe response was not JSON, or its data did not match the payment shape.NoLikely a proxy or version mismatch. Capture the requestId and contact support.
API_ERRORAn error response had no error.code. Used as a fallback.Based on statusInspect status and message.

API error codes

These arrive in the API’s error envelope and are surfaced verbatim as code. HTTP status is on error.status.

HTTPCodeMeaning
400VALIDATION_ERRORMalformed or invalid request body. Check details.
401AUTH_INVALIDMissing, malformed, revoked, or invalid credential.
403SERVICE_INACTIVEThe service is deactivated.
404PAYMENT_NOT_FOUNDNot found in this service’s scope.
409IDEMPOTENCY_CONFLICTThe same key was used for a different request.
409ORDER_CONFLICTThe external order already exists with conflicting details.
409PAYMENT_STATE_CONFLICTInvalid lifecycle operation for the current state.
413PAYLOAD_TOO_LARGEThe request body exceeds the size limit.
415UNSUPPORTED_MEDIA_TYPEThe body is not JSON.
422RETURN_URL_NOT_ALLOWEDThe return URL is not an exact allowlist match.
422RETURN_URL_REQUIREDNo return URL was supplied and no default is configured.
422GATEWAY_NOT_SUPPORTEDThe gateway code is not one OBH offers. See Gateways & installments.
422CUSTOMER_MOBILE_REQUIREDThe chosen gateway (Digipay) needs customerMobile.
503GATEWAY_NOT_AVAILABLEThe gateway has no LIVE credentials yet. Offer another one from listGateways().
429RATE_LIMITEDReserved for per-service rate limiting. Not enforced yet, but treat it as retryable.
502PROVIDER_ERRORProvider transport or response failure, sanitized.
503PROVIDER_UNAVAILABLENo active provider account or adapter.

Provider errors are sanitized

Raw provider responses, credentials, and request signing details are never passed to connected services. Treat PROVIDER_ERROR as an opaque upstream failure and retry.

The retryable flag

For API errors, retryable is true exactly when the HTTP status is 429 or >= 500. Transport failures (NETWORK_ERROR, TIMEOUT) are retryable. Everything else is not.

ErrorSafe action
retryable === true on createRetry with the same idempotency key and backoff.
retryable === true on retrieveRetry freely; reads have no side effects.
retryable === falseDo not retry unchanged. Fix the request, credential, or URL first.
INVALID_RESPONSEDo not retry blindly; the response contract is broken. Capture the request id.

Handling errors well

A good handler answers three questions: can I retry, what do I tell the customer, and what do I record for support?

A production-shaped handlerts
import { isOBHPaymentsError } from "@obhco/payments" type CheckoutFailure =  | { kind: "retry"; message: string }  | { kind: "user"; message: string; fields?: string[] }  | { kind: "support"; message: string; requestId?: string } export function classify(error: unknown): CheckoutFailure {  if (!isOBHPaymentsError(error)) {    return { kind: "support", message: "Unexpected error." }  }   switch (error.code) {    case "INVALID_ARGUMENT":    case "VALIDATION_ERROR":      return {        kind: "user",        message: "Please check the payment details.",        fields: error.details?.map((detail) => detail.field ?? ""),      }     case "AUTH_INVALID":    case "SERVICE_INACTIVE":      return {        kind: "support",        message: "Payments are temporarily unavailable.",        requestId: error.requestId,      }     case "ORDER_CONFLICT":    case "IDEMPOTENCY_CONFLICT":      return {        kind: "user",        message: "This order is already being processed.",      }     case "RETURN_URL_NOT_ALLOWED":    case "RETURN_URL_REQUIRED":      return {        kind: "support",        message: "This checkout cannot redirect back to the site.",        requestId: error.requestId,      }     default:      return {        kind: error.retryable ? "retry" : "support",        message: "We could not start the payment.",        requestId: error.requestId,      }  }}

Request IDs and support

requestId is the fastest way for support to find a failed request. Show it in an error reference for the customer, and log it next to your own order id and trace id.

It can come from the API error envelope or from the x-request-id response header. The SDK prefers the envelope value when both exist.

Logging hygiene

Never log secrets

Do not log secretKey, webhook signing secrets, Authorization headers, raw provider credentials, or full request bodies that may contain sensitive metadata. Log a request id, an error code, an order id, and a duration instead.

ts
logger.warn("obh.request.failed", {  code: error.code,  status: error.status,  requestId: error.requestId,  retryable: error.retryable,  orderId: order.id,  // Never: secretKey, authorization, raw body})

Next: the full API reference, or the troubleshooting table for specific symptoms.