Backoffice REST API — Integration Guide
Fetch the Backoffice optional skill for machine-readable operation reference: /.well-known/skills/optional/back-office.md
What is the Backoffice path?
The Backoffice path gives your server direct access to a set of payment gateway operations that do not require a physical terminal or card reader. All calls go directly to the Handpoint payment gateway — no device, no SDK, no device history.
It is available alongside any integration path — Cloud REST API, Android SDK (PAX), Android SDK (HiLite), iOS SDK, Cordova — adding server-side operations that go directly to the payment gateway with no terminal or SDK involved. Which back-office operations are available depends on acquirer support, not on which SDK you chose for card-present transactions.
When to use it
| ✅ Good fit | ❌ Not a good fit |
|---|---|
| Charging a stored card token without a physical card present (MOTO / recurring) | Card-present transactions — use Cloud API or an SDK |
| Adjusting a tip after the sale has closed | Anything that requires a cardholder to tap or insert a card |
| Closing a batch at end of day from your server | — |
| Reversing a transaction by ID without the original terminal | — |
| Querying batch totals for reconciliation | — |
How it works
Your POS Server
│
│ POST https://cloud.handpoint.com/moto/sale
│ ApiKeyCloud: YOUR_MERCHANT_API_KEY
▼
Handpoint Payment Gateway
│ (synchronous — no polling)
▼
HTTP 200 { "finStatus": "AUTHORISED", ... }
Backoffice calls are synchronous — the response is the final result. No 202 Accepted, no polling, no transactionResultId.
Authentication
All requests use the same ApiKeyCloud header as the Cloud API:
ApiKeyCloud: YOUR_MERCHANT_API_KEY
See Authentication for the full credential reference. Backoffice and Cloud API operations use the same merchant API key.
Environments
| Environment | Base URL |
|---|---|
| Development | https://cloud.handpoint.io |
| Production (DEMO + live) | https://cloud.handpoint.com |
Remote Sale (card token, no terminal)
Charge a card token obtained from a prior card-present transaction. The cardholder is not present — this is the primary use case for stored-card recurring billing.
POST https://cloud.handpoint.com/moto/sale
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"amount": "10.00",
"currency": "USD",
"cardToken": "TOKEN_FROM_PRIOR_CARD_PRESENT_TRANSACTION",
"transactionReference": "YOUR_UNIQUE_REFERENCE"
}
amount is in major currency units as a decimal string — "10.00" = $10.00.
Response — HTTP 200:
{
"@type": "sale",
"httpStatus": 200,
"acquirerTid": "ACQUIRER_TID",
"amount": "10.00",
"approvalCode": "123456",
"batchNumber": "123",
"cardTypeName": "Visa",
"currency": "USD",
"expiryDateMMYY": "1027",
"guid": "7cd7a1d0-xxxx-11f1-xxxx-xxxxxxxxxxxx",
"issuerResponseCode": "00",
"issuerResponseText": "Successful",
"maskedCardNumber": "************0936",
"retrievalReferenceNumber": "0000905343689",
"serverDateTime": "20260905204345133",
"terminalDateTime": "20260905204345000",
"transactionReference": "YOUR_UNIQUE_REFERENCE"
}
Use guid as originalGuid for subsequent reversals or linked refunds.
| Acquirer support | Notes |
|---|---|
| EPI | ✅ — Cygma token required |
| EmerchantPay | ✅ |
| Paystrax | ✅ |
| Paysafe | ❌ — Paysafe single-use tokens are not reusable for MOTO |
MOTO processing must be enabled per merchant in the Handpoint Portal (TMS) and the acquirer must have the merchant configured for card-not-present. Contact Integration Support before going live.
Error codes:
| Code | Message | Fix |
|---|---|---|
3107 | CVV required | Merchant has mandatory CVV for CNP — contact Handpoint to disable |
5252 | Card token failure | Token invalid, expired, or not found — re-tokenize via a card-present transaction |
How to obtain a card token
A cardToken is returned in any card-present TransactionResult when tokenization is enabled for the merchant. Enable it via the Handpoint Portal, then any sale or explicit tokenizeCard operation will include cardToken in the result.
See acquirer pages for token types: EPI · PAYSAFE · EmerchantPay · Paystrax
Get Card Token (EPI only): If a prior transaction was not tokenized at the time, you can retrieve the token later with no card re-swipe:
GET https://cloud.handpoint.com/transactions/{transactionID}/token
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Pass the SALE transactionID (GUID) from the original transaction result. Eligible types: sale, refund, preAuthorizationCapture, moToSale, moToRefund. Passing a reversal or void ID returns error 3112 — for a partial approval that was cancelled, use originalEFTTransactionID from the polled result to get the SALE's ID.
Response — HTTP 200:
{
"httpStatus": "200",
"cardToken": "1206598722",
"maskedCardNumber": "************0936",
"expiryDateMMYY": "1027",
"cardTokenizationGuid": "7e565290-xxxx-11f1-xxxx-xxxxxxxxxxxx",
"serverDateTime": "20260905204347641",
"agreementNumber": "111111111113",
"transactionReference": "7cd7a1d0-xxxx-11f1-xxxx-xxxxxxxxxxxx"
}
| Error | Meaning | Fix |
|---|---|---|
3112 | Transaction type not eligible — use SALE transactionID | Pass the SALE transactionID, not the reversal's |
TOKENIZATION_NOT_ENABLED | Not configured for this merchant | Contact Handpoint team |
Remote Refund (card token, no terminal)
Refund against an original remote sale by transaction ID (linked) or by card token (unlinked):
Linked refund:
POST https://cloud.handpoint.com/moto/refund
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"amount": "10.00",
"currency": "USD",
"originalGuid": "transactionID-from-original-moto-sale",
"transactionReference": "YOUR_UNIQUE_REFERENCE"
}
Unlinked refund (by card token):
POST https://cloud.handpoint.com/moto/refund
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"amount": "10.00",
"currency": "USD",
"cardToken": "STORED_TOKEN",
"transactionReference": "YOUR_UNIQUE_REFERENCE"
}
amount is in major currency units as a decimal string — "10.00" = $10.00.
| Code | Message | Fix |
|---|---|---|
3209 | Refund amount exceeds original | Reduce amount |
3210 | Currency mismatch | Use same currency as original sale |
Remote MOTO Reversal (no terminal)
Void a previous MOTO sale by transaction ID. No terminal or card is required — the gateway reverses the authorization directly.
POST https://cloud.handpoint.com/moto/reversal
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"originalGuid": "transactionID-from-original-moto-sale",
"amount": "20.00",
"currency": "USD"
}
amount is in major currency units as a decimal string — "20.00" = $20.00. amount and currency are required.
Response — HTTP 200:
{
"type": "motoReversalResponse",
"httpStatus": 200,
"amount": "20.00",
"currency": "USD",
"guid": "3f7772a0-cf88-11f0-b588-a122fae316de",
"originalGuid": "b28bdb10-cf87-11f0-b588-a122fae316de",
"issuerResponseCode": "00",
"issuerResponseText": "Successful",
"maskedCardNumber": "************3555",
"f25": "4000"
}
guid is the new reversal transaction ID. originalGuid echoes the reversed transaction.
Request parameters:
| Name | Type | Required | Description |
|---|---|---|---|
originalGuid | string | Yes | transactionID (or guid) from the original MOTO sale result |
amount | string | Yes | Major-unit decimal — "20.00" = $20.00 |
currency | string | Yes | ISO 4217, e.g. "USD", "EUR" |
customerReference | string | No | Free-text reference echoed in the result |
transactionReference | string | No | UUID v4 for your own tracking |
| Code | Message | Fix |
|---|---|---|
3153 | Unable to find message to reverse | originalGuid does not match any reversible transaction |
MOTO reversals via /moto/reversal require the same acquirer MOTO enablement as /moto/sale. Subject to acquirer reversal time windows — contact Handpoint Integration Support if reversals are rejected after the transaction window.
Tip Adjustment (EPI only)
Adjust a tip after sale, before batch close. Not supported on EmerchantPay or Paystrax — include the tip amount in the original sale body for those acquirers.
Via Cloud API (/transactions/{id}/tip-adjustment):
POST https://cloud.handpoint.com/transactions/{transactionID}/tip-adjustment
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"amount": 8
}
transactionID is the transactionID from the original sale result. amount is in major currency units (dollars/euros/etc.) — 8 means $8.00, not $0.08.
Response — HTTP 200:
{
"statusMessage": "tip adjusted"
}
Call this after the sale completes and before batch close — adjustments are not possible after the batch has closed.
If the original sale used tipConfiguration (cardholder selected tip on the terminal), do not also post a tip adjustment — it will overwrite the cardholder-selected amount.
amount: 0 — not /reversalA reversal cancels the entire sale. To remove or correct a tip, post another tip adjustment with "amount": 0. Last write before batch close wins.
See the Tipping Guide for a full comparison of tip strategies and acquirer support.
Batch Operations (EPI only)
Batch close triggers settlement with the acquirer. EU acquirers (EmerchantPay, Paystrax) use automatic settlement and do not require batch operations.
Batch Close
Closes the current open batch and triggers settlement. Omit batchNumber to target the currently open batch.
POST https://cloud.handpoint.com/batch/close
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"serialNumber": "082104578",
"deviceType": "PAXA920"
}
Response — HTTP 200:
{
"httpStatus": "200",
"batchNumber": "123",
"transactionCount": "10",
"netAmount": "1000.00",
"closedAt": "20260820172631429",
"issuerResponseCode": "00",
"issuerResponseText": "Batch closed",
"closeBatchGuid": "48c571c0-9cbc-11f1-8d8a-a5d6c6242a44",
"batchStatus": "CLOSED"
}
netAmount is in major currency units (dollars/euros). closedAt is a timestamp string in YYYYMMDDHHmmssSSS format.
Batch Summary
Retrieves aggregate totals — transaction count and net amounts by type.
POST https://cloud.handpoint.com/batch/summary
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"serialNumber": "082104578",
"deviceType": "PAXA920",
"batchNumber": "001"
}
Response — HTTP 200:
{
"httpStatus": "200",
"batchNumber": "123",
"transactionCount": "10",
"netAmount": "1000.00",
"closedAt": "20260820172630375",
"issuerResponseCode": "00",
"issuerResponseText": "Batch summary retrieved",
"batchSummaryGuid": "48207f30-9cbc-11f1-8d8a-a5d6c6242a44",
"batchStatus": "CLOSED"
}
Batch Detail
Retrieves the full list of individual transactions in a batch for reconciliation.
POST https://cloud.handpoint.com/batch/detail
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"serialNumber": "082104578",
"deviceType": "PAXA920",
"batchNumber": "001"
}
Response — HTTP 200:
{
"httpStatus": "200",
"batchNumber": "123",
"closedAt": "20260820172630912",
"issuerResponseCode": "00",
"issuerResponseText": "Batch detail retrieved",
"details": [
{
"transactionType": "SALE",
"amount": "100.00",
"batchDetailElementGuid": "b32c9185-b63f-4a14-8159-f7b5a90a8ccd"
},
{
"transactionType": "SALE",
"retrievalReferenceNumber": "RRN08236",
"amount": "50.00",
"batchDetailElementGuid": "3b4f4a8d-93da-4809-8a0d-5a75d58c9ace"
},
{
"transactionType": "REFUND",
"retrievalReferenceNumber": "RRN08237",
"amount": "25.00",
"batchDetailElementGuid": "d77933b3-a8a2-48e7-a96e-0cbf600e37d1"
}
],
"batchDetailGuid": "48746b90-9cbc-11f1-8d8a-a5d6c6242a44",
"batchStatus": "CLOSED"
}
Each entry in details has transactionType ("SALE", "REFUND", etc.), amount in major units, and an optional retrievalReferenceNumber.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
serialNumber | string | Yes | Terminal serial number |
deviceType | string | Yes | Terminal model, e.g. "PAXA920" |
batchNumber | string | No (Close) / Yes (Summary, Detail) | Omit on Batch Close to target the currently open batch |
Batch close errors:
| Code | Meaning | Action |
|---|---|---|
BATCH_ALREADY_CLOSED | Automatic close already ran | No action needed |
NO_TRANSACTIONS | No transactions in current batch | No action needed |
For EPI merchants, miss a daily batch close → BATCH_NUM_ERR_005 next day. Schedule batch close before auto-close runs (~11 PM EST) if you need manual control of settlement timing.
Remote Reversal (all acquirers)
Reverse a transaction by its original ID — no terminal required. Same-day only (before batch close).
POST https://cloud.handpoint.com/reversal
ApiKeyCloud: YOUR_MERCHANT_API_KEY
Content-Type: application/json
{
"originalGuid": "transactionID-from-original-sale"
}
originalGuid is the transactionID from the original sale result. This endpoint is synchronous — the response is the final result, no polling needed.
Response — HTTP 200:
{
"httpStatus": 200,
"acquirerTid": "ACQUIRER_TID",
"agreementNumber": "630000026730",
"amount": "150.06",
"approvalCode": "123456",
"batchNumber": "123",
"cardTypeName": "Visa",
"currency": "USD",
"issuerResponseCode": "00",
"issuerResponseText": "Successful",
"maskedCardNumber": "************0936",
"authorizationGuid": "a03b8a30-9cbb-11f1-b018-b122502914b1",
"originalGuid": "a03b8a30-9cbb-11f1-b018-b122502914b1",
"reversalGuid": "e2dd3000-9cbb-11f1-8d8a-a5d6c6242a44",
"transactionReference": "7368c0b5-e788-42de-a949-ed079946b590",
"customFields": {
"entry": [
{ "key": "messageReasonCode", "value": "4000" },
{ "key": "tenderType", "value": "Credit" },
{ "key": "issuerResponseCode", "value": "00" }
]
}
}
reversalGuid is the ID of the new reversal transaction. authorizationGuid / originalGuid both refer to the original sale. amount is in major currency units (dollars/euros). The response does not include transactionID — use reversalGuid to identify this reversal.
See Remote Reversal on the acquirer page for acquirer-specific parameters.
Operations summary
| Operation | Endpoint | Acquirer support |
|---|---|---|
| Remote Sale | POST /moto/sale | EPI, EmerchantPay, Paystrax |
| Remote Refund | POST /moto/refund | EPI, EmerchantPay, Paystrax |
| Remote MOTO Reversal | POST /moto/reversal | EPI, EmerchantPay, Paystrax |
| Get Card Token | GET /transactions/{id}/token | EPI |
| Tip Adjustment | POST /transactions/{id}/tip-adjustment | EPI |
| Partial Reversal | POST /reversal (with amount + currency) | EPI only (TMS-enabled) |
| Batch Close | POST /batch/close | EPI only |
| Batch Summary | POST /batch/summary | EPI only |
| Batch Detail | POST /batch/detail | EPI only |
| Remote Reversal | POST /reversal | All acquirers |
Validation & certification
Required for every MOTO integration:
- Card token obtained via a card-present
tokenizeCardor sale with tokenization - MOTO processing enabled in Handpoint Portal for the merchant
- CVV requirement confirmed with acquirer (disable for recurring if needed)
- Refund tested — linked by
originalGuidand unlinked bycardToken - Error codes
3107and5252handled
Required for batch operations:
- Batch close tested on DEMO merchant — no real card interaction needed
- Auto-close timing confirmed with acquirer to avoid
ERR_005
→ Error codes: Error codes