Skip to main content

Error codes

How errors are surfaced — three patterns​

Understanding which pattern an endpoint uses is the first step to handling errors correctly.

Pattern A — Asynchronous (with-reader)​

Applies to: POST /transactions (card-present sale, pre-auth, refund via terminal)

The POST always returns HTTP 202:

{ "statusMessage": "Operation Accepted", "transactionResultId": "..." }

No error is returned at POST time. Poll GET /transaction-result/{transactionResultId} to get the outcome:

Poll responseMeaning
HTTP 204Still processing — body is empty. Do not call .json() on this response. Wait and re-poll.
HTTP 200Result ready — parse the JSON body; read finStatus and statusMessage.

The error is encoded in finStatus and statusMessage of the HTTP 200 response.

Pattern B — Synchronous flat error (without-reader)​

Applies to: POST /reversal, POST /preauthorization/capture, POST /preauthorization/increase, POST /moto/sale, POST /moto/refund, POST /transactions/{id}/tip-adjustment

Error shape:

{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Human-readable description",
"code": "ERROR_CODE_HERE",
"details": { "...endpoint-specific": "data..." }
}
}

Read error.code for programmatic error identification. error.message is human-readable but may be localized.

Pattern C — Synchronous nested error (Get Card Token)​

Applies to: GET /transactions/{id}/token

The outer HTTP status is 400. The actual error code from the downstream Viscus system is two levels deep:

{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Viscus operation failed",
"details": {
"status": 403,
"body": {
"error": {
"errorCode": "3112",
"reason": "Transaction type is not eligible for deferred tokenization",
"httpStatus": "403",
"errorGuid": "..."
}
}
}
}
}

Read error.details.body.error.errorCode for programmatic identification. Do not rely on error.message — it always reads "Viscus operation failed" regardless of the underlying error.


HTTP status codes — without-reader endpoints​

HTTPnameWhen it occurs
200—Success
400BadRequestErrorBusiness logic rejection (wrong amount, already reversed, not found) — see code field
403ForbiddenErrorInvalid or missing API key
404NotFoundErrorGET /transaction-result/{id} — ID not found or expired
422UnprocessableEntityErrorRequest body validation failed — wrong field names or missing required fields; see details array
429TooManyRequestsRate limit exceeded — 2 requests per second per merchant API key. Back off and retry after 1 second. For high-throughput ISVs with multiple merchants, use a separate API key per merchant to get an independent rate limit per key.

Error codes — POST /reversal​

codemessageMeaningWhat to do
3051Already reversedTransaction has already been reversedCheck your records; no further action needed
3052Authorization has already been completedTransaction was already captured or reversed (applies to both reversal and pre-auth capture/increase)Check transaction state before acting; no further action needed
3153Unable to find message to reverse.originalGuid not foundVerify the GUID is the transactionID from the original transaction result
4066Partial reversal amount exceeds original amountamount exceeds the original transaction amountReduce amount or omit amount for a full reversal

Error codes — POST /preauthorization/capture and POST /preauthorization/increase​

codeHTTPMeaningWhat to do
3156404No pre-authorization found for the originalGuidVerify the GUID is the transactionID from the pre-auth create result
3207400The referenced transaction is not a pre-authorizationReference the Create, not an increase or a capture
3211403The pre-authorization was declined, already captured, or already reversedCheck transaction state before adjusting or capturing
3212403The decrease would take the hold to zero or belowSend a Pre-Auth Reversal to release the hold in full
3215403The capture amount exceeds the current hold totalIncrease the hold first, then capture
5001400NullPointerException — internal error surfaced for unknown GUIDs on these endpointsVerify the GUID is the transactionID from the pre-auth create result
note

On POST /preauthorization/increase the amount field is increaseAmount (not amount) and takes a decimal major-unit string, e.g. "20.00". Sending amount returns 422 VALIDATION_FAILED.

For how increases and decreases accumulate, which GUID to reference, and the per-path decrease signal, see the Pre-Authorization Guide.

Transaction result finStatus values — with-reader operations​

These appear in the polled GET /transaction-result/{id} response. statusMessage is localized (based on card and terminal locale) — use finStatus for all programmatic branching.

Complete finStatus enum — all possible values:

finStatusFinal?Meaning
IN_PROGRESSNoStill processing on the terminal or gateway. Keep polling.
UNDEFINEDNo*Terminal sent the transaction but no result was received. Follow UNDEFINED recovery.
AUTHORISEDYesApproved.
DECLINEDYesDeclined by gateway or acquirer.
FAILEDYesTechnical failure. Card may or may not have been charged — follow recovery flow before retry.
PARTIAL_APPROVALNo†Approved for less than the requested amount (US acquirers only). Terminal is showing an accept/decline prompt — result is not final until cardholder responds.
CANCELLEDYesCardholder or merchant cancelled at terminal.
PROCESSEDYesCompleted for non-financial operations (tokenizeCard, MOTO tokenization).
REFUNDEDYesRefund processed successfully.
CAPTUREDYesPre-authorization captured.

*UNDEFINED is treated as non-final until the recovery flow produces a definitive result.

†PARTIAL_APPROVAL is not final when returned from /status. The terminal is showing an accept/decline prompt to the cardholder — the result can change to CANCELLED if they decline (SDK auto-reverses). Continue polling transaction-result for at least 60 seconds or until the final result is delivered. See Partial Approval — timing edge case.

Common DECLINED patterns with diagnostic signals:

statusMessagearcCauseWhat to do
Invalid Merchant1000Wrong externalId in merchantAuth, or MID misconfiguredReturn statusMessage to the merchant; see Multi-MID guide
Card declined the online authorization (or similar locale-dependent wording)—EMV forced reversal: card was removed mid-chip before the full EMV flow completed, or the card's internal application declined (e.g. IAD mismatch). SDK auto-reversed the authorized hold. /status may have shown AUTHORISED briefly — the final transaction-result is DECLINED.Display statusMessage to the merchant. Ask the cardholder to re-insert the card and leave it in until the terminal confirms. Do not retry without cardholder action.
UNABLE_TO_FIND_MESSAGE_TO_REVERSE.—originalTransactionId not found in the open batchVerify GUID; batch may have closed — send a Refund instead
PARTIAL_REVERSAL_AMOUNT_EXCEEDS_ORIGINAL_AMOUNT—Reversal amount exceeds original saleUse the exact original sale amount
(localized refund amount error)—Linked refund amount exceeds original — card IS prompted before this errorPre-validate amount on ISV side before sending
(issuer message, locale-dependent)—Issuer declined — acquirer pass-throughDisplay statusMessage to the merchant as-is; use finStatus for programmatic logic

Common FAILED patterns:

statusMessageCauseWhat to do
Transaction failed, error: Error getting advanced transaction status (transaction not found)...originalTransactionId not found (pre-auth reversal path)Verify GUID
Read card errorCard could not be readAsk cardholder to retry; try insert if tap failed
HMAC mismatchMOTO not enabled (supportsMoto = false)ViscusDummy-specific; real acquirers may return DECLINED

Immediate errors — with-reader operations​

These are returned in the initial POST before the 202 is issued:

HTTPmessageMeaningWhat to do
403No valid key found in headerInvalid or missing API keyCheck the ApiKeyCloud header value
400{"error":1001,"message":"Device is busy"}Terminal is processing another operationWait and retry; implement a short backoff (2–5s)
400{"error":1002,"message":"No device listening at the other end of the secure channel"}Terminal is not connected to the Handpoint Cloud channel — powered off, not on Wi-Fi, or Payments App not runningCheck terminal power, Wi-Fi, and that the Handpoint Payments App is open
400{"error":1004,"message":"Auth not available: ..."}Terminal serial or terminal_type is not assigned to the merchant account for this API keyVerify the terminal is assigned in Handpoint Portal; check GET /devices to see which serials are valid for this API key
400{"error":1003,"message":"Cancel operation not allowed"}cancelRequest was sent when no cancellable operation is in progressOnly call cancelRequest while an operation is actively running on the terminal
400{"error":1005,"message":"No transaction to cancel"}cancelRequest was received but no transaction is active on the terminalVerify the terminal state before sending a cancel
400TransactionReference with wrong uuidv4 format ...transactionReference is not a valid UUID v4Generate a compliant UUID v4 — version digit (position 13) must be 4, variant digit (position 17) must be 8, 9, a, or b. See transactionReference usage

Error codes — Remote Sale back-office endpoints​

These errors are returned synchronously by the remote sale back-office endpoints (POST /moto/sale, POST /moto/refund). All return HTTP 400 Bad Request with a structured error body:

{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "<description>",
"code": "<code>",
"details": {
"errorCode": "<code>",
"description": "<description>",
"errorGuid": "<guid>",
"httpStatus": <status>
}
}
}

POST /moto/sale errors​

codemessageMeaningWhat to do
3107CVV requiredThe merchant account has "CVV/CV2 input mandatory" configured for Card Not Present, but the remote sale no-reader endpoint cannot accept a CVV.Contact Handpoint to disable mandatory CVV for this merchant's remote sale configuration, or use a terminal-based (on-terminal) remote sale flow instead.
5252Card token failureThe card token provider (Cygma, etc.) is temporarily down or unreachable. The stored cardToken is valid — tokens do not expire. (details.httpStatus is 404 internally.)Retry the charge when the provider recovers. If persistent (>5 min), contact Handpoint to verify token provider availability.

POST /moto/refund errors​

codemessageMeaningWhat to do
3209The requested refund amount is greater than the initial sale amountamount in the refund request exceeds the amount of the original sale referenced by originalGuid.Reduce the refund amount to at most the original sale amount.
3210Original and linked currency do not matchThe currency in the refund request does not match the currency recorded on the original sale.Use the same currency as the original sale.

Error codes — Get Card Token (GET /transactions/{id}/token)​

Error shape: Pattern C (nested — read error.details.body.error.errorCode).

EPI only. Requires a SALE transactionID — not a reversal ID, not a pre-auth ID.

errorCodereasonMeaningWhat to do
3112Transaction type is not eligible for deferred tokenizationThe transactionID in the URL is not a SALE. Common cause: using the reversal's transactionID after a partial-approval → cancel flow.Use the SALE transactionID. On a partial-approval → cancel, the polled result's transactionID is the reversal — use originalEFTTransactionID from that result instead, or call GET https://cloud.handpoint.com/{transactionReference}/status/all and pick the entry where type == "SALE".
TOKENIZATION_NOT_ENABLEDNot configured for this merchantMerchant does not have card tokenization enabled.Contact Handpoint Integration Support to enable tokenization on the merchant account.
Cancelled and reversed transactions are tokenizable

A SALE that was later reversed or cancelled (e.g. partial approval declined by cardholder) can still be tokenized — the card was read and encrypted during EMV processing before the reversal. Use the original SALE transactionID, not the reversal's.

Error codes — Tip Adjustment (POST /transactions/{id}/tip-adjustment)​

Error shape: Pattern B (flat — read error.code). EPI only. Tip adjustment is available before the current batch closes.

ConditionHTTPBehaviourWhat to do
Batch already closed400Error returned — specific code depends on acquirerTip adjustments cannot be reversed after batch close; only pre-batch-close adjustments are possible
transactionID not found400Error returnedVerify the transactionID matches the transactionID field in the original sale result (not transactionReference)
Amount is 0400Validation errorSend the tip amount as a non-zero integer in major currency units (e.g. 8 = $8.00, not cents)

On success: HTTP 200 with body {"statusMessage": "tip adjusted"}.

Error codes — Fee Mitigation​

The gateway refuses these requests before the authorization, so the transaction never reaches the acquirer. They apply to a sale, a Remote Sale and a pre-authorization capture that carries a fee object. See Fee Mitigation for the contract.

CodeMeaningRecovery
4258 SURCHARGE_NOT_ENABLEDThe merchant is not configured to surcharge, and the request carries a surcharge — through fee with mitigationProgram: surcharge, or through the deprecated surchargeAmountAsk Handpoint to enable surcharging for the merchant, or send no fee
4268 FEE_SURCHARGE_AMOUNT_CONFLICTThe request carries both fee and the deprecated surchargeAmount, and they disagree. Accepted only when mitigationProgram is surcharge and the two amounts matchMigrate the call site completely. Send one field or the other
4269 FEE_TAX_ON_FEE_EXCEEDS_L2_TAXfee.taxOnFee is greater than the tax carried by taxInformation.taxAmountLower taxOnFee, or declare the full tax. Subtracting more tax than was declared would put a negative tax on the wire
FIELD_REQUIREDfee.amount or fee.mitigationProgram is missingSend both. Neither one is optional
AMOUNT_FORMATfee.amount or fee.taxOnFee is malformed or negativeSend a positive amount in the documented format
MITIGATION_PROGRAM_INVALIDfee.mitigationProgram is not one of the four valuesSend surcharge, adminFee, cashDiscount or dualPricing. The gateway never falls back to a default

A fee that the gateway drops is not an error. The transaction continues, and fee.applied is false with a fee.reason that explains it. See why the fee was applied or dropped.

The Android SDK checks the first three rules before it sends, and raises a verification error instead of starting a transaction.

UNDEFINED status​

finStatus: UNDEFINED means the terminal sent the transaction to the gateway but no result was received. The transaction may or may not have been processed — do not retry.

Recovery — in order:

  1. Call the status endpoint — fastest path:

    GET https://transactions.handpoint.com/transactions/{transactionReference}/status

    Returns the final finStatus if the gateway has the result. If IN_PROGRESS, poll every 10 s. If UNDEFINED, proceed to step 2.

  2. Query the Transaction Feed API — if the status endpoint returns UNDEFINED or if transactionReference was not echoed (e.g. on-terminal MOTO — see known issue CUS-837): query the Transaction Feed API by terminal serial number and the approximate transaction time window to locate the settled record.

  3. Android SDK alternative: hapi.getTransactionStatus(transactionReference) wraps step 1 without a direct HTTP call.

→ Full recovery flow with code examples: Transaction Recovery — Cloud API

PAX terminals only

UNDEFINED recovery applies to PAX terminals. HiLite BT handles disconnection differently — the SDK buffers and retries delivery automatically.