Skip to main content

Cloud API — Operations Reference

Complete request/response examples for every Cloud API operation. Each section shows the full two-step flow — the curl command, the 202 acceptance, the poll, and the final result — alongside a parameter table and the most common error responses.

For authentication, environments, and callback vs polling delivery, see the Cloud API Integration Guide.
For error code definitions and error shapes, see Error codes.


Response pattern​

All terminal operations follow a two-step model. Step 1 is synchronous; step 2 is polled.

Step 1 — send the operation

curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "operation": "sale", "amount": "15012", ... }'
// HTTP 202 — accepted immediately; terminal begins processing
{
"statusMessage": "Operation Accepted",
"transactionResultId": "1850025030-1788700677769",
"transactionReference": "5c7056aa-b0a6-4ee9-891e-aae6ce7ea725"
}

Step 2 — poll for the result

curl https://cloud.handpoint.com/transaction-result/1850025030-1788700677769 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — final result; finStatus other than IN_PROGRESS means complete
{
"finStatus": "AUTHORISED",
...
}

Poll at ~3s intervals until finStatus is not IN_PROGRESS. The terminal can take up to 6 minutes for a full fallback path (contactless fail → chip fail → swipe → wrong PINs).

Exception — gateway-synchronous: POST /reversal and POST /preauthorization/capture return their result directly (no poll).


Sale​

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "sale",
"amount": "15012",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"transactionReference": "5c7056aa-b0a6-4ee9-891e-aae6ce7ea725"
}'
// HTTP 202
{
"statusMessage": "Operation Accepted",
"transactionResultId": "1850025030-1788700677769",
"transactionReference": "5c7056aa-b0a6-4ee9-891e-aae6ce7ea725"
}
# Step 2 — poll (use transactionResultId from step 1)
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700677769 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"authorisationCode": "123456",
"transactionID": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"transactionReference": "5c7056aa-b0a6-4ee9-891e-aae6ce7ea725",
"maskedCardNumber": "************0936",
"cardSchemeName": "VISA",
"tenderType": "CREDIT",
"cardEntryType": "ICC",
"paymentScenario": "CHIP",
"requestedAmount": 15012,
"totalAmount": 15012,
"currency": "USD",
"rrn": "0000906190164",
"issuerResponseCode": "00",
"arc": "0000",
"type": "SALE",
"customerReceipt": "https://receipts.handpoint.com/receipts/67905570-a9f5-11f1-a943-f9c9f04151d9/customer.html",
"merchantReceipt": "https://receipts.handpoint.com/receipts/67905570-a9f5-11f1-a943-f9c9f04151d9/merchant.html"
}

Request parameters

ParameterTypeRequiredDefaultDescription
operationstringYes—"sale"
serial_numberstringYes—Terminal serial number
terminal_typestringYes—PAX model — e.g. "PAXA920PRO". Valid values
amountstringYes—Minor-unit amount as a string — "1000" = $10.00. Digits only, max 12 characters.
currencystringYes—ISO 4217 alpha-3 — "USD", "GBP", "EUR"
transactionReferencestringRecommendedauto-UUIDUUID v4. Persist before sending — required for recovery via GET /transactions/{ref}/status. Only honoured for sale, refund, saleAndTokenizeCard, preAuthorization.
callbackUrlstringNo—HTTPS endpoint. Result POSTed here when complete.
tokenstringNo—Sent as auth-token header on the callback POST. Use a unique value per request.
customerReferencestringNo—Free-text order reference stored with the transaction.
duplicate_checkbooleanNotrueWhen true (default), the terminal checks whether the same transactionReference was used recently. If a duplicate is detected, a 30-second confirmation prompt is shown on the terminal screen — the merchant must accept or decline. Accept → a new authorisation request is sent to the gateway; the final result is delivered normally. Decline → finStatus: CANCELLED is delivered immediately; no card charge. Set false only when you are intentionally replaying a reference (e.g. after recovering an UNDEFINED result and confirming the transaction was not charged).
bypassOptionsobjectNo—{ "signatureBypass": bool, "pinBypass": bool } — see bypassOptions
tipConfigurationobjectNo—Tip selection screen — see tipConfiguration
merchantAutharrayNo—Multi-MID credential override — see merchantAuth
metadataobjectNo—{ "metadata1": "…", …, "metadata5": "…" } — max 250 chars each
moneyRemittanceOptionsobjectNo—EmerchantPay only — see moneyRemittanceOptions
billingobjectNo—AVS — { "zipCode": string (required), "address": string (optional) }

Sale and Tokenize​

Simultaneously runs a sale and tokenizes the card for future MOTO/back-office use. Requires a card token provider configured on the merchant.

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "saleAndTokenizeCard",
"amount": "1215",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"transactionReference": "b777a62d-9bfb-4b6a-b188-7f842a930770"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700034700 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"authorisationCode": "123456",
"cardToken": "K33f40000000000093",
"transactionID": "e3303350-a9f3-11f1-a943-f9c9f04151d9",
"maskedCardNumber": "************0936",
"cardSchemeName": "VISA",
"tenderType": "CREDIT",
"cardEntryType": "ICC",
"paymentScenario": "CHIPCONTACTLESS",
"requestedAmount": 1215,
"totalAmount": 1215,
"currency": "USD",
"type": "SALE"
}

The cardToken value (e.g. "K33f40000000000093") is the Cygma token format. Store it for subsequent MOTO operations.

Request parameters — same as Sale except:

ParameterTypeRequiredNotes
operationstringYes"saleAndTokenizeCard"
tipConfiguration——Not valid for saleAndTokenizeCard — omit it

All other Sale parameters apply. tipConfiguration is not valid for saleAndTokenizeCard — omit it.

Error — token provider not configured​

// HTTP 200 poll — DECLINED
{
"finStatus": "DECLINED",
"statusMessage": "Card token failure",
"errorMessage": "",
"cardToken": "",
"transactionID": "",
"requestedAmount": 0,
"type": "SALE"
}
Detection signalValue
finStatusDECLINED
transactionIDEmpty string — acquirer was never contacted
requestedAmount0 — no amount was processed
cardTokenEmpty string

Tokenize Card​

No-charge card tokenization. The card is presented at the terminal, tokenized, and no payment is taken. The token is returned in cardToken in the poll result. finStatus is PROCESSED on success.

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "tokenizeCard",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030"
}'
// HTTP 202
{
"statusMessage": "Operation Accepted",
"transactionResultId": "1850025030-1788700678000"
}
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700678000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — PROCESSED
{
"finStatus": "PROCESSED",
"statusMessage": "Approved or completed successfully",
"cardToken": "K33f40000000000093",
"maskedCardNumber": "************0936",
"cardSchemeName": "VISA",
"cardEntryType": "ICC",
"type": "TOKENIZE_CARD"
}

Request parameters

ParameterTypeRequiredDescription
operationstringYes"tokenizeCard"
serial_numberstringYesTerminal serial number
terminal_typestringYesPAX model — valid values
customerReferencestringNoFree-text reference echoed in the result
callbackUrlstringNoHTTPS webhook endpoint
tokenstringNoCallback auth token

Amount and currency are not required — no charge is made. transactionReference is not honoured for tokenizeCard.


Pre-Authorization​

Places a hold on funds without capturing them. Requires preAuthAllowed = true on the merchant.

# Step 1 — create the hold
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "preAuthorization",
"amount": "10000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"transactionReference": "e4826edd-7579-48ca-9553-743e5b76d855"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700629963 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED (hold placed, no funds captured)
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"authorisationCode": "123456",
"transactionID": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f",
"requestedAmount": 10000,
"totalAmount": 10000,
"type": "PRE_AUTHORIZATION"
}

Pre-Auth create — request parameters

ParameterTypeRequiredDefaultDescription
operationstringYes—"preAuthorization"
serial_numberstringYes—Terminal serial number
terminal_typestringYes—PAX model — valid values
amountstringYes—Minor-unit string — "10000" = $100.00. Digits only.
currencystringYes—ISO 4217
transactionReferencestringRecommendedauto-UUIDUUID v4. Links all subsequent operations via /status/all.
callbackUrlstringNo—HTTPS webhook endpoint
tokenstringNo—Callback auth token
customerReferencestringNo—Free-text reference
duplicate_checkbooleanNotrueWhen true (default), the terminal checks whether the same transactionReference was used recently. If a duplicate is detected, a 30-second confirmation prompt is shown on the terminal — accept sends a new authorisation, decline delivers finStatus: CANCELLED. Set false only when intentionally replaying a reference after an UNDEFINED recovery.
bypassOptionsobjectNo—{ "signatureBypass": bool, "pinBypass": bool }
merchantAutharrayNo—Multi-MID override — see merchantAuth
metadataobjectNo—Up to 5 string fields, max 250 chars each
# Capture — use transactionID from the hold result as originalGuid
curl -X POST https://cloud.handpoint.com/preauthorization/capture \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f",
"capturedAmount": "10000",
"currency": "USD"
}'
// HTTP 200 — AUTHORISED (funds captured)
{
"finStatus": "AUTHORISED",
"type": "PRE_AUTHORIZATION_CAPTURE"
}

POST /preauthorization/capture — request parameters

ParameterTypeRequiredDefaultDescription
originalGuidstringYes—transactionID from the Pre-Authorization create result. Max 64 chars.
capturedAmountstringYes—Amount to capture. Max 32 chars. ⚠️ See unit note below.
tipAmountstringNo—Optional tip to add to the capture. Same unit as capturedAmount. Max 32 chars.
customerReferencestringNo—Free-text reference. Max 64 chars.
capturedAmount uses major units (decimal)

Pass capturedAmount as a major-unit decimal string — e.g. "45.00" for $45.00, "120.00" for $120.00. This matches increaseAmount and the /reversal amount field. It is not minor units.

POST /preauthorization/increase — request parameters

ParameterTypeRequiredDefaultDescription
originalGuidstringYes—transactionID from the Pre-Authorization create result. Max 64 chars.
increaseAmountstringYes—Delta to add (or subtract). Always a positive value. Major-unit decimal — e.g. "5.00" = $5.00. Max 32 chars.
subtractstringNo—Pass "1" to decrease the hold instead of increase. "1" is the only accepted value.
tipAmountstringNo—Tip amount adjustment. Same unit as increaseAmount. Max 32 chars.
customerReferencestringNo—Free-text reference. Max 64 chars.

Error — pre-auth not enabled​

// HTTP 200 poll — DECLINED
{
"finStatus": "DECLINED",
"statusMessage": "Pre-authorizations are not enabled for this terminal",
"errorMessage": "",
"arc": "1000",
"cardEntryType": "ICC",
"type": "PRE_AUTHORIZATION",
"transactionID": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f",
"issuerResponseCode": "00"
}

Error — capture exceeds hold amount​

curl -X POST https://cloud.handpoint.com/preauthorization/capture \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f",
"capturedAmount": "15000",
"currency": "USD"
}'
// HTTP 400 — synchronous rejection
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Capture amount cannot be greater than pre-auth hold amount",
"code": "3215",
"details": { "errorCode": "3215", "httpStatus": 403 }
}
}

Error — pre-auth already captured or reversed​

// HTTP 400 — synchronous rejection
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Authorization has already been completed",
"code": "3052",
"details": { "errorCode": "3052", "httpStatus": 409 }
}
}

Pre-Authorization Reversal (on terminal)​

Releases a pre-authorization hold without capturing it. The terminal must be connected — the card may need to be re-presented depending on the acquirer.

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "preAuthorizationReversal",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "4b2c4470-a9f5-11f1-99ee-c974d92ef76f"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700630100 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED (hold released)
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "4f9d2200-a9f5-11f1-99ee-c974d92ef76f",
"type": "PRE_AUTHORIZATION_REVERSAL"
}

Request parameters

ParameterTypeRequiredDescription
operationstringYes"preAuthorizationReversal"
serial_numberstringYesTerminal serial number
terminal_typestringYesPAX model — valid values
originalTransactionIdstringYestransactionID from the original pre-authorization result
customerReferencestringNoFree-text reference
Gateway-level reversal without a terminal

To release a pre-auth hold remotely (without a terminal), use POST /reversal with the originalGuid set to the pre-auth transactionID. See Reversal.


Refund​

Card-present refund. Requires refundAllowed = true on the merchant. For linked refunds, include originalTransactionId.

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "refund",
"amount": "5000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"transactionReference": "eb6a9a5e-ad43-4014-a8a0-b0b7243169d5"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700667457 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "5e88eeb0-a9f5-11f1-a943-f9c9f04151d9",
"requestedAmount": 5000,
"totalAmount": 5000,
"type": "REFUND"
}

Request parameters

ParameterTypeRequiredDefaultDescription
operationstringYes—"refund"
serial_numberstringYes—Terminal serial number
terminal_typestringYes—PAX model — valid values
amountstringYes—Minor-unit string — "5000" = $50.00. Digits only.
currencystringYes—ISO 4217
transactionReferencestringRecommendedauto-UUIDUUID v4. Persist before sending.
originalTransactionIdstringNo—transactionID from the original sale. Include to link the refund (same-card enforcement).
callbackUrlstringNo—HTTPS webhook endpoint
tokenstringNo—Callback auth token
customerReferencestringNo—Free-text reference
duplicate_checkbooleanNotrueWhen true (default), the terminal checks whether the same transactionReference was used recently. If a duplicate is detected, a 30-second confirmation prompt is shown on the terminal — accept sends a new authorisation, decline delivers finStatus: CANCELLED. Set false only when intentionally replaying a reference after an UNDEFINED recovery.
bypassOptionsobjectNo—{ "signatureBypass": bool, "pinBypass": bool }
merchantAutharrayNo—Multi-MID override
metadataobjectNo—Up to 5 string fields, max 250 chars each

Linked refund — add originalTransactionId to refund against a specific sale and require the same card:

curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "refund",
"amount": "5000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"transactionReference": "eb6a9a5e-ad43-4014-a8a0-b0b7243169d5"
}'

Error — refunds not enabled​

// HTTP 200 poll — DECLINED
{
"finStatus": "DECLINED",
"statusMessage": "Refund not allowed",
"errorMessage": "",
"arc": "0000",
"cardEntryType": "ICC",
"type": "REFUND",
"transactionID": "5e88eeb0-a9f5-11f1-a943-f9c9f04151d9"
}
Acquirer variation

TSYSDummy rejects refunds synchronously with HTTP 400 when refundAllowed=false. ViscusDummy forwards to the terminal (202) and declines after card read. Handle both code paths.

Error — wrong card on linked refund​

When refundOriginalCardOnly = true, the refund must be performed on the original card.

// HTTP 200 poll — DECLINED
{
"finStatus": "DECLINED",
"statusMessage": "ORIGINAL_CARD_REQUIRED_FOR_REFUND",
"cardEntryType": "ICC",
"type": "REFUND"
}

statusMessage: "ORIGINAL_CARD_REQUIRED_FOR_REFUND" is an English constant on this error path — safe to match programmatically.

Error — transaction not refundable​

When a sale was processed while refundAllowed = false, the transaction is permanently flagged as non-refundable. This flag persists even after re-enabling the capability.

Cannot be fixed by changing TMS configuration

Escalate to support@handpoint.com — the Handpoint operations team can manually clear the non-refundable flag on specific transactions.

Refund Reversal (on terminal)​

Reverses a previously issued refund, restoring the original refunded amount to the merchant. The terminal must be connected.

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "refundReversal",
"amount": "5000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "5e88eeb0-a9f5-11f1-a943-f9c9f04151d9"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700668000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "6c22a900-a9f5-11f1-a943-f9c9f04151d9",
"requestedAmount": 5000,
"totalAmount": 5000,
"type": "REFUND_REVERSAL"
}

Request parameters

ParameterTypeRequiredDescription
operationstringYes"refundReversal"
serial_numberstringYesTerminal serial number
terminal_typestringYesPAX model — valid values
amountstringYesAmount in minor units. For acquirers that support partial reversal, the specified amount is reversed. For others, the full original refund amount is reversed regardless of this value.
currencystringYesISO 4217 currency code
originalTransactionIdstringYestransactionID from the refund to reverse
customerReferencestringNoFree-text reference

Reversal​

Reverses all or part of an authorized transaction. Full reversals are always available. Partial reversals require partialReversalAllowed = true. The response is synchronous — no polling needed.

# Full reversal
curl -X POST https://cloud.handpoint.com/reversal \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"amount": "150.12",
"currency": "USD"
}'
// HTTP 200 — AUTHORISED (synchronous)
{
"finStatus": "AUTHORISED",
"transactionID": "6a1d41e0-a9f5-11f1-a943-f9c9f04151d9",
"type": "REVERSAL"
}

Omit amount (or pass the full original amount) for a full reversal. For a partial reversal, pass the specific amount to release.

POST /reversal — request parameters

ParameterTypeRequiredDefaultDescription
originalGuidstringYes—transactionID from the original AUTHORISED sale result
amountstringNoFull original amountMajor-unit decimal — "50.04" = $50.04. Omit for full reversal. Required for partial reversal.
currencystringNo—ISO 4217. Required when amount is provided.
messageReasonCodestringNo"CUSTOMER_CANCELLATION"Reason for the reversal. "CUSTOMER_CANCELLATION" (default) — cardholder or merchant initiated. "TIMEOUT_WAITING_FOR_RESPONSE" — use when reversing because you did not receive an authorization response in time.

Error — partial reversal not enabled​

The only capability enforced at the gateway HTTP layer — the terminal is never involved.

curl -X POST https://cloud.handpoint.com/reversal \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"amount": "50.04",
"currency": "USD"
}'
// HTTP 400 — synchronous rejection
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Partial reversals are not supported",
"code": "3109",
"details": { "errorCode": "3109", "reason": "Partial reversals are not supported" }
}
}

Error — transaction not found​

curl -X POST https://cloud.handpoint.com/reversal \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"originalGuid": "00000000-0000-0000-0000-000000000000",
"currency": "USD"
}'
// HTTP 400
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Unable to find message to reverse.",
"code": "3153",
"details": { "errorCode": "3153", "httpStatus": 404 }
}
}

Error — already reversed​

// HTTP 400
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Authorization has already been completed",
"code": "3052",
"details": { "errorCode": "3052", "httpStatus": 409 }
}
}

MOTO / Card-Not-Present​

MOTO uses a stored card token. Requires supportsMoto = true on the merchant.

OperationEndpointResponse model
Keyed entry sale / pre-auth / refund on terminalPOST /transactionsHTTP 202 → poll
Remote sale (back-office, no terminal)POST /moto/saleSynchronous
Remote refund (back-office, no terminal)POST /moto/refundSynchronous

MOTO sale — keyed entry on terminal​

# Step 1 — initiate (cardToken from a previous saleAndTokenizeCard)
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "moToSale",
"amount": "2000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"cardToken": "K33f40000000000093",
"transactionReference": "a1b2c3d4-0000-4000-8000-000000000001"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700645247 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"authorisationCode": "123456",
"transactionID": "57cd25a0-a9f5-11f1-a943-f9c9f04151d9",
"paymentScenario": "MOTO",
"cardEntryType": "CNP",
"requestedAmount": 2000,
"totalAmount": 2000,
"type": "MOTO_SALE"
}

POST /transactions — moToSale — request parameters

ParameterTypeRequiredDefaultDescription
operationstringYes—"moToSale"
serial_numberstringYes—Terminal serial number
terminal_typestringYes—PAX model — valid values
amountstringYes—Minor-unit string — "2000" = $20.00. Digits only.
currencystringYes—ISO 4217
cardTokenstringNo—Stored card token. If omitted, terminal shows manual card-entry screen.
channelstringNo—"MO" (mail order) or "TO" (telephone order)
callbackUrlstringNo—HTTPS webhook endpoint
tokenstringNo—Callback auth token
customerReferencestringNo—Free-text reference
merchantAutharrayNo—Multi-MID override
metadataobjectNo—Up to 5 string fields, max 250 chars each
billingobjectNo—AVS — { "zipCode": string (required), "address": string (optional) }
transactionReference bug on moToSale (CUS-837)

The transactionReference you send is ignored by the Cloud API for moToSale — the returned result contains a system-generated reference that does not match your value. Recovery via GET /transactions/{ref}/status will not work for keyed-entry MOTO. Use transactionResultId to poll instead, and store transactionID from the result for recovery. Status: open as of 2026-09-06.

MOTO refund — keyed entry on terminal​

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "moToRefund",
"amount": "2000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "57cd25a0-a9f5-11f1-a943-f9c9f04151d9"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700646000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "5f3a1100-a9f5-11f1-a943-f9c9f04151d9",
"paymentScenario": "MOTO",
"cardEntryType": "CNP",
"requestedAmount": 2000,
"totalAmount": 2000,
"type": "MOTO_REFUND"
}

POST /transactions — moToRefund — request parameters

ParameterTypeRequiredDescription
operationstringYes"moToRefund"
serial_numberstringYesTerminal serial number
terminal_typestringYesPAX model — valid values
amountstringYesMinor-unit string — "2000" = $20.00. Digits only.
currencystringYesISO 4217
originalTransactionIdstringNotransactionID from the original MOTO sale for a linked refund. Omit for unlinked.
cardTokenstringNoStored card token. If omitted, terminal shows manual card-entry screen.
customerReferencestringNoFree-text reference

MOTO reversal — keyed entry on terminal​

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "moToReversal",
"amount": "2000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"originalTransactionId": "57cd25a0-a9f5-11f1-a943-f9c9f04151d9"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700647000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "6031c200-a9f5-11f1-a943-f9c9f04151d9",
"paymentScenario": "MOTO",
"type": "MOTO_REVERSAL"
}

POST /transactions — moToReversal — request parameters

ParameterTypeRequiredDescription
operationstringYes"moToReversal"
serial_numberstringYesTerminal serial number
terminal_typestringYesPAX model — valid values
amountstringYesMinor-unit string — "2000" = $20.00. Digits only.
currencystringYesISO 4217
originalTransactionIdstringYestransactionID from the original MOTO sale to reverse
customerReferencestringNoFree-text reference

MOTO pre-authorization — keyed entry on terminal​

# Step 1 — initiate
curl -X POST https://cloud.handpoint.com/transactions \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "moToPreAuthorization",
"amount": "10000",
"currency": "USD",
"terminal_type": "PAXA920PRO",
"serial_number": "1850025030",
"cardToken": "K33f40000000000093"
}'
# Step 2 — poll
curl https://cloud.handpoint.com/transaction-result/1850025030-1788700648000 \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — AUTHORISED (hold placed, no funds captured)
{
"finStatus": "AUTHORISED",
"statusMessage": "Approved or completed successfully",
"transactionID": "6132b300-a9f5-11f1-a943-f9c9f04151d9",
"paymentScenario": "MOTO",
"cardEntryType": "CNP",
"requestedAmount": 10000,
"totalAmount": 10000,
"type": "MOTO_PRE_AUTHORIZATION"
}

POST /transactions — moToPreAuthorization — request parameters

ParameterTypeRequiredDescription
operationstringYes"moToPreAuthorization"
serial_numberstringYesTerminal serial number
terminal_typestringYesPAX model — valid values
amountstringYesMinor-unit string — "10000" = $100.00. Digits only.
currencystringYesISO 4217
cardTokenstringNoStored card token. If omitted, terminal shows manual card-entry screen.
customerReferencestringNoFree-text reference

Capture and release the hold using POST /preauthorization/capture and POST /reversal respectively, with the transactionID from this result as originalGuid.

MOTO remote sale — back-office (no terminal)​

curl -X POST https://cloud.handpoint.com/moto/sale \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "20.00",
"currency": "USD",
"cardToken": "K33f40000000000093",
"transactionReference": "a1b2c3d4-0000-4000-8000-000000000002"
}'
// HTTP 200 — synchronous result
{
"finStatus": "AUTHORISED",
"authorisationCode": "123456",
"transactionID": "7ef31400-a9f5-11f1-a943-f9c9f04151d9",
"paymentScenario": "MOTO",
"requestedAmount": 2000,
"totalAmount": 2000,
"type": "MOTO_SALE"
}

POST /moto/sale — request parameters

ParameterTypeRequiredDefaultDescription
amountstringYes—Major-unit decimal string — "20.00" = $20.00. Pattern: ^\d+(\.\d+)?$. This endpoint uses major units, unlike POST /transactions which uses minor units.
currencystringYes—ISO 4217. Exactly 3 characters.
cardTokenstringYes—Stored card token from a prior saleAndTokenizeCard. Max 64 chars.
transactionReferencestringNo—UUID v4 recommended. Max 50 chars.
customerReferencestringNo—Free-text reference. Max 50 chars.
channelstringNo—"MO" (mail order) or "TO" (telephone order)
billingobjectNo—AVS — { "zipCode": string (required), "address": string (optional) }. Postal code and optional street address forwarded to the acquirer for address verification. Requires avsForMoto enabled for the merchant. See AVS.

POST /moto/refund — request parameters

ParameterTypeRequiredDefaultDescription
originalGuidstringYes—transactionID from the original MOTO sale
amountstringYes—Major-unit decimal string. Pattern: ^\d+(\.\d+)?$.
currencystringYes—ISO 4217. Exactly 3 characters.
transactionReferencestringNo—UUID v4 recommended. Max 50 chars.
customerReferencestringNo—Free-text reference. Max 50 chars.
channelstringNo—"MO" or "TO"

Error — MOTO not enabled (keyed entry path)​

// HTTP 200 poll — FAILED (not DECLINED)
{
"finStatus": "FAILED",
"statusMessage": "HMAC mismatch",
"errorMessage": "HMAC mismatch",
"paymentScenario": "MOTO",
"cardEntryType": "CNP",
"type": "MOTO_SALE",
"transactionID": "57cd25a0-a9f5-11f1-a943-f9c9f04151d9"
}

finStatus: "FAILED" — not DECLINED. ViscusDummy returns a technical failure when MOTO is disabled. Real acquirers may return DECLINED. Always branch on finStatus, not message text.


Recovery — check transaction status​

Use this when a server restart, network drop, or crash means you missed the callback or poll result.

Different base URL

The status endpoint is on transactions.handpoint.com, not cloud.handpoint.com. Using the wrong host returns 404.

PurposeBase URL
Send transactions (POST) / poll by transactionResultId (GET)cloud.handpoint.com
Check status by transactionReferencetransactions.handpoint.com
curl https://transactions.handpoint.com/transactions/5c7056aa-b0a6-4ee9-891e-aae6ce7ea725/status \
-H "ApiKeyCloud: YOUR_API_KEY"
// HTTP 200 — resolved
{
"finStatus": "AUTHORISED",
"transactionID": "67905570-a9f5-11f1-a943-f9c9f04151d9",
"totalAmount": 15012,
"currency": "USD",
"type": "SALE"
}

The path parameter is your transactionReference (the UUID you set). This endpoint does not return customerReceipt / merchantReceipt URLs — build the receipt from the result fields if needed.

# Query the full operation chain (sale + all subsequent operations)
curl https://transactions.handpoint.com/transactions/5c7056aa-b0a6-4ee9-891e-aae6ce7ea725/status/all \
-H "ApiKeyCloud: YOUR_API_KEY"

Receipts​

Receipt URLs are returned in every completed poll result:

{
"customerReceipt": "https://receipts.handpoint.com/receipts/67905570-a9f5-11f1-a943-f9c9f04151d9/customer.html",
"merchantReceipt": "https://receipts.handpoint.com/receipts/67905570-a9f5-11f1-a943-f9c9f04151d9/merchant.html"
}

The path uses transactionID (gateway-assigned GUID), not transactionReference.

ConditionReceipt value
Normal transactionHosted URL
SDK lost connection before response"" (empty string)
S3 upload failure or MOTO on-terminalFull HTML string embedded in the field
finStatus: UNDEFINED"" (empty string)

Detecting embedded HTML: customerReceipt.startsWith("<") rather than "https://".

For compliance field requirements, language behaviour, and building a receipt from the /status endpoint, see Receipt Compliance.


Device management​

Remote device management commands for PAX terminals. All commands require the Handpoint Payments App to be running in Integrated Mode (enabled via Handpoint TMS). Commands are asynchronous — the 202 Accepted response confirms delivery; the command executes on the device shortly after.

Endpoint pattern: POST https://cloud.handpoint.com/devices/{deviceType}/{serialNumber}/{command}

Common headers:

HeaderRequiredDescription
ApiKeyCloudYesMerchant API key
Content-TypeYesapplication/json

Common response codes:

CodeDescription
202Command accepted and will be executed
400Device not listening — offline or Payments App not in Integrated Mode
403Authentication failed
422Invalid request body

Set Unattended Mode​

POST /devices/{deviceType}/{serialNumber}/set-unattended-mode

Enables or disables unattended mode. When enabled, the Android navigation bar (Home, Back, Recent) is hidden and only the Payment screen is accessible — Settings, History, and Analytics tabs are not reachable.

Body fieldTypeRequiredDescription
statusbooleanYestrue to enable unattended mode, false to disable
curl -X POST https://cloud.handpoint.com/devices/PAXA920PRO/1850025030/set-unattended-mode \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": true }'
HTTP/1.1 202 Accepted

Set Locale​

POST /devices/{deviceType}/{serialNumber}/set-locale

Sets the display language and region on the terminal.

Body fieldTypeRequiredDescription
localestringYesIETF BCP 47 language tag — e.g. "en_US", "en_CA", "fr_CA", "es_ES"
curl -X POST https://cloud.handpoint.com/devices/PAXA920PRO/1850025030/set-locale \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "locale": "en_US" }'
HTTP/1.1 202 Accepted

Reboot​

POST /devices/{deviceType}/{serialNumber}/reboot

Reboots the terminal. Use force: false (default) to check whether a transaction is in progress before rebooting.

Body fieldTypeRequiredDescription
forcebooleanYestrue to reboot immediately even if a transaction is in progress. false to check status first — if a transaction is active, the reboot may be deferred.
curl -X POST https://cloud.handpoint.com/devices/PAXA920PRO/1850025030/reboot \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "force": false }'
HTTP/1.1 202 Accepted

Set Screen Brightness​

POST /devices/{deviceType}/{serialNumber}/set-screen-brightness

Sets the minimum and maximum screen brightness levels. Both values must be integers between 0 and 100.

Body fieldTypeRequiredDescription
minimumBrightnessLevelintegerYesMinimum brightness (0–100)
maximumBrightnessLevelintegerYesMaximum brightness (0–100)
curl -X POST https://cloud.handpoint.com/devices/PAXA920PRO/1850025030/set-screen-brightness \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "minimumBrightnessLevel": 20, "maximumBrightnessLevel": 100 }'
HTTP/1.1 202 Accepted

Set Reboot Time​

POST /devices/{deviceType}/{serialNumber}/set-reboot-time

Schedules a daily automatic reboot at a given hour. The actual reboot occurs at a random minute within the specified hour to spread device restarts across a fleet.

Production devices only

This command is only active on production devices. It has no effect on development/staging terminals.

Body fieldTypeRequiredDescription
hourintegerYesHour of day (0–23) when the device should reboot. The reboot occurs at a random minute within that hour.
curl -X POST https://cloud.handpoint.com/devices/PAXA920PRO/1850025030/set-reboot-time \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "hour": 22 }'
HTTP/1.1 202 Accepted

Set Password Protected​

POST /devices/{deviceType}/{serialNumber}/set-password-protected

Enables or disables password protection on the terminal's Payments App settings screen.

Body fieldTypeRequiredDescription
statusbooleanYestrue to enable password protection, false to disable
curl -X POST https://cloud.handpoint.com/devices/PAXA920PRO/1850025030/set-password-protected \
-H "ApiKeyCloud: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": true }'
HTTP/1.1 202 Accepted
Android SDK version requirement

Device management commands require Android SDK version 7.1006.0 or later on the terminal.


Parameter reference​

terminal_type values​

All PAX devices that support the Cloud API (isAndroidPayment: true). Both the long form and short alias are accepted — "PAXA920PRO" and "A920PRO" are equivalent.

ValueShort aliasNotes
PAXA920A920—
PAXA920PROA920PROHas printer
PAXA910A910Has printer
PAXA910SA910SHas printer
PAXA920MAXA920MAXHas printer
PAXA930A930Has printer
PAXA960A960Has printer
PAXA6650A6650Has printer
PAXA8700A8700Has printer
PAXA8900A8900Has printer
PAXA80A80Has printer and keyboard
PAXA800A800Has printer
PAXA30A30Has keyboard
PAXA35A35Has keyboard
PAXA50A50—
PAXA60A60—
PAXA77A77—
PAXA3700A3700—
PAXA6630A6630—
PAXARIES6ARIES6Has keyboard
PAXARIES8ARIES8Has keyboard
PAXE500E500Has printer
PAXE600E600Has printer
PAXE700E700Has printer
PAXE800E800Has printer
PAXIM25IM25—
PAXIM30IM30—
TELPOTPS900TPS900Telpo — has printer

HiLite / DATECS devices (HILITE, MPED400, etc.) use Bluetooth and do not work with the Cloud API.


operation values​

ValueDescription
saleCard-present sale
refundCard-present refund
saleAndTokenizeCardSale + card tokenization
tokenizeCardTokenize only — no charge
moToSaleMOTO keyed entry on terminal
moToRefundMOTO refund on terminal
moToPreAuthorizationMOTO pre-auth on terminal
moToReversalMOTO reversal on terminal
preAuthorizationPre-auth hold (card present)
preAuthorizationIncreaseAdjust hold amount
preAuthorizationCaptureCapture hold (also: dedicated POST /preauthorization/capture)
preAuthorizationReversalVoid / release hold
saleReversalOn-terminal reversal of a sale
refundReversalOn-terminal reversal of a refund
stopCurrentTransactionCancel the active terminal operation
pingDeviceConnectivity check
printReceiptPrint a receipt on the terminal

transactionReference is only honoured for sale, refund, saleAndTokenizeCard, preAuthorization. For all other operation values it is stripped and replaced with a system-generated reference.

originalTransactionId is required for: saleReversal, refundReversal, moToReversal, preAuthorizationIncrease, preAuthorizationCapture, preAuthorizationReversal.


bypassOptions​

Both fields are required when the bypassOptions object is included.

FieldTypeDefaultDescription
signatureBypassbooleanfalseThe signature screen is not shown to the cardholder at all — signature input is bypassed entirely, not just skippable.
pinBypassbooleanfalseShows the PIN screen but the cardholder can skip by pressing the green key without entering a PIN. Records verificationMethod: PIN_BYPASS in the result.
Chip-enforced PIN cards ignore pinBypass

When a card's EMV configuration requires PIN verification, the terminal enforces it regardless of pinBypass: true. Acquirer configurations may also restrict bypass — confirm with your acquirer before deploying.


tipConfiguration​

Valid for sale only (not saleAndTokenizeCard, moToSale, etc.).

FieldTypeRequiredDefaultDescription
baseAmountstringNoTransaction amountAmount used to calculate percentage buttons. Minor units, digits only.
headerNamestringNo"Tip"Header text on the tip screen.
tipPercentagesinteger[]Yes—Percentage buttons to display — e.g. [15, 18, 20]. Must be non-empty when tipConfiguration is included.
enterAmountEnabledbooleanNotrueShow a "custom amount" entry option.
skipEnabledbooleanNotrueShow a "no tip / skip" button.
footerstringNo""Footer text on the tip screen.

tipAmount appears in the result (not the request) with the amount the cardholder selected, in minor units.


merchantAuth​

An array of Credential objects for multi-MID scenarios. Overrides the merchant's default credentials for a specific acquirer. At most one credential per acquirer.

Each Credential object:

FieldTypeRequiredDescription
externalIdstringYesHandpoint-assigned sub-merchant ID. Must exactly match a subMerchantExternalId provisioned by the Handpoint onboarding team. Max 23 chars. No other field may be present in the same object.

See Multi-MID for full usage and testing guidance.


metadata​

FieldTypeMax lengthDescription
metadata1string250Custom field 1
metadata2string250Custom field 2
metadata3string250Custom field 3
metadata4string250Custom field 4
metadata5string250Custom field 5

moneyRemittanceOptions​

EmerchantPay only. Required for MasterCard remittance transactions (MCC 4829 / MCC 6540). Omit entirely for Visa — Visa handles remittance at the network level without extra fields.

FieldTypeRequiredConstraintsDescription
fullNamestringYesMax 30 charsRecipient full name
countryCodestringYesISO 3166-1 alpha-3Recipient destination country — "GBR", "USA", "DEU"