Sentinel Service Mode
How an ECR, till or third-party application drives a Sentinel payment terminal — over local HTTP, over a USB serial cable, or through Android intents.
| Setting | Value |
|---|---|
| Application | Sentinel 4.0.8 |
| Package | com.paylands.posms.sentinel |
| Intent scheme | com.paynopain.sentinelservice |
| Local port | 6889 |
1. Introduction
Sentinel exposes its payment operations to other applications so a cash register can take a card payment without anyone touching the terminal. This document is the contract: what you can call, what you must send, and what comes back.
Three transports carry the same operations and return the same payloads. Pick the one that matches how your till reaches the terminal:
- Local HTTP — your software runs on the terminal itself, or on the same host, and posts JSON to
127.0.0.1:6889. - USB serial — your till is a separate machine and sees the terminal as a COM port.
- Android intents — your software is another app on the same Android terminal.
Not every operation reaches every transport, and the availability differs per operation. Each entry below opens with the three transports marked as available or not, so you can see at a glance what a given integration can call.
Service mode. The terminal only answers external operations while service mode is enabled in its configuration. When it is off, operations are refused with a fixed JSON body and nothing is charged. Health check is the sole exception — it answers either way, so a till polling for presence can tell a terminal that is switched off from one that is merely not configured.
2. Package name and intent scheme
Sentinel Service used to ship as a separate application under the package com.paynopain.sentinelservice. Service mode is now part of the main Sentinel app, and the package name changed with it.
| Use | Value |
|---|---|
| Package name | Current package. Use this for PackageManager installation checks and anywhere you reference the installed app: com.paylands.posms.sentinel |
| Intent scheme | Unchanged, for backward compatibility. Keep using it in every intent URI: com.paynopain.sentinelservice:// |
Update your installation check. Intent URIs need no changes, but any pre-flight check that looks for com.paynopain.sentinelservice as an installed package will now report the app as missing. Point it at com.paylands.posms.sentinel. The old name survives only as a URI scheme.
3. Transports
3.1 Local HTTP
POST JSON to a per-operation path on port 6889. There is no single dispatch endpoint — the path is the operation.
POST http://127.0.0.1:6889/payment
Content-Type: application/json
Authorization: Bearer <token supplied by Paynopain>
{"amount":100,"currencyCode":978,"stan":"7f328336-f3fa-4e37-92d5-b7f1311c0f4d"}
The server answers 200 OK with the operation's result as the body, or 404 Not Found for an unknown path. Successful card operations return the transaction result JSON described in §24; validation failures return a plain-text message (§4.4).
Paths currently served:
/payment /preauthorization /transaction
/refund /confirmation /transaction_by_stan
/referral /cancellation /list_transactions
/reversal /decrement /reports
/cardverify /cft /close_batch
/healthcheck /identify /reprint
/sync
3.2 HTTPS
The same port serves TLS once a certificate has been provisioned to the terminal. The certificate and private key arrive through device configuration as a base64 PKCS#12 keystore; until one is present the server listens in plain HTTP, so existing integrations keep working unchanged. Check the terminal's service status screen to see which is active.
3.3 USB serial
The terminal enumerates as a USB serial device and your till opens it as a COM port. Supported on PAX terminals; other models report no ECR link and the USB server does not start.
| Setting | Value |
|---|---|
| Line | 115200 baud, 8 data bits, no parity, 1 stop bit |
| Framing | 4-byte big-endian length header, then that many bytes of UTF-8 JSON |
| Request | {"id":"…","type":"payment","body":{ …same fields as HTTP… }} |
| Response | {"id":"…","type":"payment","result":"…"} |
The id is echoed back so you can match responses to requests. type takes the same names as the HTTP paths without the leading slash. One operation runs at a time: a second request arriving mid-operation is answered immediately with {"error":"busy: another operation is in progress"} rather than being queued or dropped. Health check is answered by the reader thread, so it stays responsive even while a payment is waiting on the cardholder.
3.4 Android intents
Launch a view intent and read the result in onActivityResult. Authorization is handled by whitelisting your application with the Paynopain team rather than by a token.
String uri = "com.paynopain.sentinelservice://payment?amount=100¤cyCode=978&stan=" + stan;
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
startActivityForResult(intent, PAYMENT_RESULT_CODE);
Results come back as two string extras: transactionResult carries the operation payload, and error_message explains a cancellation or failure. See §26 for a full example.
4. Conventions
4.1 Amounts
Every amount is an integer in the currency's minor units — 100 is one euro. ISK, which has no minor unit, is the exception and is sent as whole króna.
4.2 STAN
The stan is your reference for the operation, and you generate it: a UUID, unique per request. It is echoed in the result and it is the key you use to recover an operation whose response you never received — see §16. Reusing a STAN makes that recovery ambiguous.
4.3 Currency
currencyCode is the ISO 4217 numeric code — 978 euro, 840 US dollar, 826 pound sterling. It must match the currency the terminal is configured for; a mismatch is refused before anything reaches the card.
4.4 Errors
Validation and policy failures return a plain-text string, not JSON. Test for these before parsing a result body:
| Response | Meaning |
|---|---|
| Error: Missing required fields — … | Named fields were absent or empty. The list follows the dash. |
| Error: No data received in the request | Empty request body. |
| Max Amount Exceeded | Above the terminal's configured ceiling. With a fixed tip, the ceiling applies to base plus tip. |
| Currency is not allowed | currencyCode is not the terminal's currency. |
| Too much pending transactions. Please sync. | The offline queue is full. Call sync, then retry. |
| Tokenization is not enabled for this merchant | tokenize: true on a merchant without tokenization. |
| Operative not allowed | The operation is not available on this terminal's operator. |
| Error: … | An unexpected failure; the exception message follows. |
These strings are localised. A terminal set to Spanish returns Error: Campos requeridos faltantes rather than the English above. Do not match on the full text — match on a prefix you control, or treat any non-JSON body as a failure.
Two responses are JSON rather than plain text. Service mode disabled:
{"error":"Service mode disabled: terminal does not accept external operations"}
And, on the USB transport only, a second concurrent operation:
{"error":"busy: another operation is in progress"}
4.5 Timeouts
An operation that never produces a result — an abandoned payment, a dropped socket — is abandoned after 100 seconds and returns a timeout error rather than holding the transport open. Printing is capped at 30 seconds and reports as not printed. Size your own client timeout above 100 seconds so you see the terminal's answer rather than your own.
5. Payment
Charge a debit or credit card. Beyond a plain sale, the same operation covers tipping, QR payment and per-sale tokenization.
Operation: payment
| Transport | Operation | Available |
|---|---|---|
| HTTP | /payment | Yes |
| USB | payment | Yes |
| Intent | //payment | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| amount | Integer | Required | Minor units. Excludes a fixed tip, which is added on top. |
| currencyCode | Integer | Required | ISO 4217 numeric. Must match the terminal. |
| stan | String | Required | Your UUID for this operation. |
| reference | String | Optional | Free-text reference stored with the transaction and shown on the receipt. |
| customer_ext_id | String | Optional | Your customer identifier, echoed back in the result. |
| tip | Boolean or Integer | Optional | true prompts for a tip on the terminal screen. An integer is a fixed tip in minor units, added to amount, with no prompt. Omit for no tip. |
| channel | String | Optional | card (default) or qr. |
| qrMethod | String | Optional | With channel: "qr", fixes the QR method (for example BIZUM) and skips the on-screen picker. Omit to let the cashier choose. |
| tokenize | Boolean | Optional | Overrides the terminal's tokenization switch for this sale only. Omit to follow the terminal setting. |
Response
The transaction result payload (§24), once the card operation completes.
Example
POST /payment
{"amount":100,"currencyCode":978,"stan":"7f328336-f3fa-4e37-92d5-b7f1311c0f4d"}
com.paynopain.sentinelservice://payment
?amount=100¤cyCode=978&stan=ebdd1b6a-02f4-4366-9a9e-d19758123746
5.1 Tipping
tip is overloaded deliberately, because the two tipping models are genuinely different operations from your side:
"tip": truebrings Sentinel to the foreground and asks the cardholder for a tip on the terminal. Your call blocks until the whole flow finishes and then returns the result as usual."tip": 150adds a fixed 1.50 tip with no screen and no interaction. The amount charged isamount + tip, and that total is what must stay under the terminal's ceiling.
5.2 QR payment
With channel: "qr" the terminal displays a QR code, which the customer may take minutes to scan. Rather than hold your connection open, the call returns immediately:
{"status":"QR_SHOWN","stan":"…","message":"QR mostrado en el terminal. Consulta el resultado con transaction_by_stan."}
Poll get by STAN with the same stan to collect the outcome.
5.3 Tokenization
tokenize decides per sale instead of following the terminal-wide switch. Two limits are worth knowing before you rely on it:
- The merchant's own tokenization flag still wins. Asking for a token on a merchant without tokenization is refused outright rather than charging silently without one.
- Per-sale control only reaches the ECOPAYNET route. On MONET the payment app decides from the merchant configuration, so
tokenize: falsewill not suppress a token that the merchant is configured to receive.
6. Preauthorization
Reserve an amount on a card without capturing it. Settle it later with a confirmation, reduce it with a decrement, or release it with a cancellation.
Operation: preauthorization
| Transport | Operation | Available |
|---|---|---|
| HTTP | /preauthorization | Yes |
| USB | preauthorization | Yes |
| Intent | //preauthorization | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| amount | Integer | Required | Amount to reserve, in minor units. |
| currencyCode | Integer | Required | ISO 4217 numeric. |
| stan | String | Required | Your UUID for this operation. |
| reference | String | Optional | Free-text reference. |
| customer_ext_id | String | Optional | Your customer identifier. |
Response
Transaction result with operative of DEFERRED. Keep its transaction_id, auth_code and refund_id — the follow-up operations need them.
7. Confirmation
Capture a preauthorization, in full or for less than was reserved.
Operation: confirmation
| Transport | Operation | Available |
|---|---|---|
| HTTP | /confirmation | Yes |
| USB | confirmation | Yes |
| Intent | //confirmation | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| amount | Integer | Required | Amount to capture. May be less than reserved. |
| currencyCode | Integer | Required | ISO 4217 numeric. |
| parentIdentifier | String | Required | transaction_id of the preauthorization. |
| stan | String | Required | Your UUID for this operation. |
| date | String | Conditional | Required on ECOPAYNET. Date of the original operation. |
| authCode | String | Conditional | Required on ECOPAYNET. From the preauthorization result. |
| refundId | String | Conditional | Required on ECOPAYNET. From the preauthorization result. |
| customer_ext_id | String | Optional | Your customer identifier. |
Response
Transaction result with operative of CONFIRMATION.
The conditional fields depend on the terminal's operator. On ECOPAYNET, date, authCode and refundId are mandatory and their absence is reported as a missing-fields error. On other operators they are ignored. If you do not know which operator a terminal runs, call identify — it returns the operator name — or simply always send all three.
8. Cancellation
Release a preauthorization without capturing it.
Operation: cancellation
| Transport | Operation | Available |
|---|---|---|
| HTTP | /cancellation | Yes |
| USB | cancellation | Yes |
| Intent | //cancellation | Yes |
Availability: Service mode.
Request
Same fields as confirmation: amount, currencyCode, parentIdentifier and stan are required; date, authCode and refundId are required on ECOPAYNET; customer_ext_id is optional.
Response
Transaction result with operative of CANCELLATION.
9. Decrement
Lower the amount held by a preauthorization while keeping the reservation open — the case where a final bill lands under the estimate but the tab is still running.
Operation: decrement
| Transport | Operation | Available |
|---|---|---|
| HTTP | /decrement | Yes |
| USB | decrement | Yes |
| Intent | //decrement | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| amount | Integer | Required | Amount currently held by the preauthorization. |
| decrementAmount | Integer | Required | Amount to release, in minor units. |
| currencyCode | Integer | Required | ISO 4217 numeric. |
| parentIdentifier | String | Required | transaction_id of the preauthorization. |
| stan | String | Required | Your UUID for this operation. |
| authCode | String | Optional | From the preauthorization result. |
| refundId | String | Optional | From the preauthorization result. |
| date | String | Optional | Date of the original operation. |
| customer_ext_id | String | Optional | Your customer identifier. |
Response
Transaction result with operative of DECREMENT.
10. Refund
Return money to the card used for an earlier sale. The card must be presented.
Operation: refund
| Transport | Operation | Available |
|---|---|---|
| HTTP | /refund | Yes |
| USB | refund | Yes |
| Intent | //refund | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| amount | Integer | Required | Amount to return, in minor units. |
| currencyCode | Integer | Required | ISO 4217 numeric. |
| parentIdentifier | String | Required | transaction_id of the original sale. |
| refundId | String | Required | refund_id from the original sale's result. |
| stan | String | Required | Your UUID for this operation. |
| date | String | Conditional | Required on ECOPAYNET. |
| authCode | String | Conditional | Required on ECOPAYNET. |
| customer_ext_id | String | Optional | Your customer identifier. |
Example
POST /refund
{"amount":100,"currencyCode":978,
"parentIdentifier":"9c76341c-fcbd-492d-8b7a-ed14d0fc6a8d",
"refundId":"1248529069100765184",
"stan":"79f5ae33-cad9-45f4-983e-d7a9a8fc1cc9"}
11. Referral refund
Refund an earlier sale without the card present, using the original transaction's credentials.
Operation: referral
| Transport | Operation | Available |
|---|---|---|
| HTTP | /referral | Yes |
| USB | referral | Yes |
| Intent | //referral | Yes |
Availability: Service mode.
Request
Identical to refund: amount, currencyCode, parentIdentifier, refundId and stan are required; date and authCode are required on ECOPAYNET; customer_ext_id is optional.
Response
Transaction result with operative of REFERRAL_REFUND.
Example
com.paynopain.sentinelservice://referral
?amount=100¤cyCode=978
&parentIdentifier=1e7da5d3-4aec-4f97-9029-4dce55216964
&refundId=1334440644315197440
&stan=234ec44e-a377-4380-a4fb-bf15933301e0
12. Reversal
Void a sale on the same day it was taken, before it settles. Cheaper and faster than a refund, and it leaves no refund on the cardholder's statement.
Operation: reversal
| Transport | Operation | Available |
|---|---|---|
| HTTP | /reversal | Yes |
| USB | reversal | Yes |
| Intent | //reversal | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| currencyCode | Integer | Required | ISO 4217 numeric. |
| parentIdentifier | String | Required | transaction_id of the sale being voided. |
| refundId | String | Required | refund_id from the original sale's result. |
| stan | String | Required | Your UUID for this operation. |
| customer_ext_id | String | Optional | Your customer identifier. |
No amount: a reversal always voids the full original amount.
Response
Transaction result with operative of REVERSAL. On ECOPAYNET terminals the operation is not supported and returns Operative not allowed.
13. Card verify
Read and validate a card without charging it, and receive a reusable token for it.
Operation: cardverify
| Transport | Operation | Available |
|---|---|---|
| HTTP | /cardverify | Yes |
| USB | cardverify | Yes |
| Intent | //cardverify | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| currencyCode | Integer | Required | ISO 4217 numeric. |
| stan | String | Required | Your UUID for this operation. |
| dni | String | Optional | Cardholder identity document. |
| reference | String | Optional | Free-text reference. |
| customer_ext_id | String | Optional | Your customer identifier. |
Response
Transaction result with operative of CARD_VERIFY, plus the token fields card_token, par and transaction_type. The amount is zero.
Example
POST /cardverify
{"currencyCode":978,"stan":"5ad8fb83-9540-4a46-bfaa-d0b3f9bbc196"}
14. CFT — credit to card
Pay money onto a beneficiary's card. The amount is not yours to set: the backend fixes it from the order token, and the terminal only confirms it against the card.
Operation: cft
| Transport | Operation | Available |
|---|---|---|
| HTTP | /cft | Yes |
| USB | cft | Yes |
| Intent | //cft | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| token | String | Required | Order token issued by the backend. Determines the amount. |
| name | String | Required | Beneficiary given name. |
| surname | String | Required | Beneficiary surname. |
| String | Required | Beneficiary email. | |
| stan | String | Required | Your UUID for this operation. |
15. Get transaction
Look up a transaction on the backend by its identifier.
Operation: transaction
| Transport | Operation | Available |
|---|---|---|
| HTTP | /transaction | Yes |
| USB | transaction | Yes |
| Intent | //transaction | Yes |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| transactionId | String | Required | The transaction's identifier. |
| stan | String | Required | Your UUID for this lookup. |
Response
{
"id": "665d6dea126521c8926056ef",
"identifier": "919ff6d6-7727-4801-9035-9f57d2e8041a",
"merchant": "649c0329052f100dceea50e9",
"device": "64d1ef302ff0833b33ecffe6",
"operative": "AUTHORIZATION",
"status": "PAID",
"amount": 100,
"currency": "978",
"incoming": "DEVICE",
"integrator": "615d7d35be2246344cc49adf",
"integrator_external_id": "919FF6D6-7727-4801-9035-9F57D2E8041A",
"created_at": "2024-06-03T07:16:58.183Z",
"updated_at": "2024-06-03T07:16:58.384Z",
"historic": [{ "status": "PAID", "date": "2024-06-03T07:16:58.181Z" }]
}
16. Get transaction by STAN
Recover an operation using the STAN you generated. This is how you resolve a payment whose response you never received, and how you collect the outcome of a QR payment.
Operation: transaction_by_stan
| Transport | Operation | Available |
|---|---|---|
| HTTP | /transaction_by_stan | Yes |
| USB | transaction_by_stan | Yes |
| Intent | //transactionbystan | Yes |
Availability: Service mode.
The intent host is transactionbystan, without underscores. The HTTP path and USB type are transaction_by_stan, with them.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| stan | String | Required | The STAN you sent with the original operation. |
Response while the operation is still running
Sentinel answers from its local pending queue, so an operation still in flight is reported rather than reported missing:
{
"identifier": "4ad6624f-8ad4-4ef7-8557-d41616770f63",
"operative": "AUTHORIZATION",
"amount": 100,
"currency": "978",
"incoming": "DEVICE",
"response_status": "Pending",
"response_message": "In progress",
"stan": "2fdf5900-22ae-46b1-8398-8fcda2bcce04"
}
Response once it has finished
The full backend record, with the card result nested under result:
{
"id": "67a5f4e1d0a4c0353b3e04d6",
"identifier": "4ad6624f-8ad4-4ef7-8557-d41616770f63",
"merchant": "66e80b75a1a978f055c8fe07",
"device": "64d1ef302ff0833b33ecffe6",
"operative": "AUTHORIZATION",
"status": "PAID",
"amount": 100,
"currency": "978",
"incoming": "DEVICE",
"result": {
"aid": "A0000000031010",
"amount": 100,
"currency": "978",
"auth_code": "050107",
"brand": "VISA",
"pan": "476173******0119",
"reversal": false,
"date_terminal": "2025-02-07T11:56:17.836Z",
"date_server": "2025-02-07T11:56:17.836Z",
"response_status": "PAID",
"card_input": "CLESS",
"cardholder_method": ["[SIGNATURE]"],
"response_message": "OK: success",
"terminal_acquirer": "TPPESNA004",
"merchant_acquirer": "TPPESNA004",
"refund_id": "1337391474924330111",
"stan": "2fdf5900-22ae-46b1-8398-8fcda2bcce04"
},
"created_at": "2025-02-07T11:56:17.872Z",
"updated_at": "2025-02-07T11:56:18.452Z",
"historic": [{ "status": "PAID", "date": "2025-02-07T11:56:17.862Z" }]
}
17. List transactions
Retrieve recent transactions to reconcile a till or to recover after an interruption — the case where the STAN itself was lost and the single-transaction lookups cannot help.
Operation: list_transactions
| Transport | Operation | Available |
|---|---|---|
| HTTP | /list_transactions | Yes |
| USB | list_transactions | Yes |
| Intent | — | No |
Availability: Service mode.
Request
All fields optional; an empty body returns the 50 most recent.
| Field | Type | Requirement | Notes |
|---|---|---|---|
| limit | Integer | Optional | Default 50, clamped to 200. |
| dateFrom | String | Optional | Lower bound. |
| dateTo | String | Optional | Upper bound. |
| status | String | Optional | One TransactionStatus value. An unrecognised value returns an error listing the accepted ones. |
Response
{
"status": "OK",
"count": 2,
"limit": 50,
"truncated": false,
"transactions": [
{
"identifier": "919ff6d6-7727-4801-9035-9f57d2e8041a",
"amount": 100,
"remaining_amount": null,
"currency": "978",
"operative": "AUTHORIZATION",
"status": "PAID",
"created_at": "2026-08-31T09:14:22Z",
"auth_code": "478893",
"refund_id": "1305910960162082816",
"parent_id": null,
"pan": "476173******0119",
"reference": null,
"aid": "A0000000031010",
"brand": "VISA",
"tip_amount": 0,
"is_qr": false,
"token": null,
"customer_ext_id": null
}
]
}
Dates are ISO-8601 in UTC. There is no paging: truncated is true when exactly limit rows came back, meaning more may exist. Narrow with dateFrom and dateTo rather than expecting a cursor.
18. Reports
Totals for the period still open — the equivalent of an X report. Reads only; it changes nothing.
Operation: reports
| Transport | Operation | Available |
|---|---|---|
| HTTP | /reports | Yes |
| USB | reports | Yes |
| Intent | — | No |
Availability: Service mode.
Request
No body.
Response
{
"status": "OK",
"dateFrom": "2026-08-31 06:00:00",
"transactions": 42,
"totalAmount": 128400,
"authorizations": 38,
"totalAuthorizationsAmount": 131900,
"refunds": 2,
"totalRefundsAmount": 3500,
"deferred": 1,
"totalDeferredAmount": 5000,
"confirmations": 1,
"totalConfirmedAmount": 5000,
"cancelled": 0,
"totalCancelledAmount": 0
}
All totals are in minor units, covering everything since dateFrom — the cut-off set by the last close.
19. Close batch
Close the till period — the equivalent of a Z report. Prints the report and moves the cut-off forward so the next report starts where this one ends.
Operation: close_batch
| Transport | Operation | Available |
|---|---|---|
| HTTP | /close_batch | Yes |
| USB | close_batch | Yes |
| Intent | — | No |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| Boolean | Optional | Default true. Set false to close without printing, when the till prints its own copy. |
Response
The same totals as reports, with status of CLOSED plus cutoff and printed.
This does not settle the batch with the acquirer. Acquirer settlement is initiated by the backend, not the terminal. Close batch does exactly what the button in the app does: print the report and advance the local cut-off date. Treat it as a till operation, not a financial one.
20. Reprint receipt
Print a receipt again, or have the backend issue it as a link — for when the paper ran out, the cut was crooked, or the customer comes back ten minutes later.
Operation: reprint
| Transport | Operation | Available |
|---|---|---|
| HTTP | /reprint | Yes |
| USB | reprint | Yes |
| Intent | — | No |
Availability: Service mode.
Request
| Field | Type | Requirement | Notes |
|---|---|---|---|
| transactionId | String | Optional | Omit to reprint the most recent transaction. Searched among the last 50. |
| copy | String | Optional | client (default) or merchant. |
| giftTicket | Boolean | Optional | Default false. With true, this print omits the subtotal, tip and total. It does not modify the transaction or future prints. |
| String | Optional | Sends the receipt by email instead of printing, and returns its URL. |
Response
{"status":"PRINTED","identifier":"919ff6d6-…","copy":"client","giftTicket":false}
{"status":"SENT","identifier":"919ff6d6-…","email":"a@b.com","url":"https://…"}
{"status":"NOT_FOUND","message":"…"}
{"status":"ERROR","message":"El terminal no pudo imprimir (sin papel, sin impresora o error)"}
The printer only reports success. A terminal with no paper, or no printer at all, produces ERROR after the 30-second print timeout rather than hanging.
Every printed copy also includes a QR code containing only the transaction UUID. In the interactive app, the operator can scan it from the Transactions top bar to retrieve and open that transaction; no amount or card data is encoded in the QR.
21. Health check
Ask whether the terminal is up and the transport is reaching it. The only operation that answers regardless of service mode.
Operation: healthcheck
| Transport | Operation | Available |
|---|---|---|
| HTTP | /healthcheck | Yes |
| USB | healthcheck | Yes |
| Intent | //healthcheck | Yes |
Availability: Always available.
Response
The literal string READY or NOT_READY — not JSON.
This deliberately answers a different question from "will you take a payment". A till polling for presence needs to tell a terminal that is off, or a cable that is loose, from one that is powered up and simply not in service mode. For the latter, ask for an operation and read the service-mode error.
On USB, health check is answered by the reader thread rather than the worker, so it stays responsive while a payment is waiting on the cardholder.
22. Identify
Ask the terminal who it is. Usually the first call an integration makes: you need the identifier to match your receipts against the backend's, and the serial number for your own inventory.
Operation: identify
| Transport | Operation | Available |
|---|---|---|
| HTTP | /identify | Yes |
| USB | identify | Yes |
| Intent | — | No |
Availability: Service mode.
Response
{
"status": "OK",
"identifier": "352966108076163",
"serialNumber": "1853237957",
"model": "A920Pro",
"manufacturer": "PAX",
"appVersion": "4.0.8",
"operator": "MONET"
}
A terminal that has not finished configuration returns {"status":"ERROR","message":"Terminal sin identificador: falta configuración"}. operator is what tells you whether the ECOPAYNET conditional fields apply.
23. Sync
Flush the offline queue. Without this, a terminal that hits its pending limit refuses every operation and, in service mode, has nobody standing in front of it to clear the jam.
Operation: sync
| Transport | Operation | Available |
|---|---|---|
| HTTP | /sync | Yes |
| USB | sync | Yes |
| Intent | //sync | Yes |
Availability: Service mode.
Response
{"status":"NOTHING_TO_SYNC","pendingBefore":0,"pendingAfter":0}
{"status":"SYNCED","pendingBefore":7,"pendingAfter":0}
{"status":"IN_PROGRESS","pendingBefore":7,"pendingAfter":3,"message":"…"}
{"status":"ERROR","message":"…","pendingBefore":7}
Sync starts the flush; it does not wait for it. The terminal cannot know when the backend has finished, so it reports the pending count before and after a short settling window and returns. IN_PROGRESS means work continues in the background — retry your operation, and call sync again if it is still refused. Do not treat SYNCED as a guarantee that every queued transaction reached the backend.
24. Transaction result payload
What every card operation returns. Field names are snake_case, with panExpiryDate the lone exception.
| Field | Type | Requirement | Notes |
|---|---|---|---|
| device | String | Always | Terminal identifier. |
| transaction_id | String | Always | Backend identifier. Use as parentIdentifier in follow-ups. |
| stan | String | Always | The STAN you sent. |
| operative | Operative | Always | See §25. |
| amount | Integer | Always | Minor units. Includes the tip when there is one. |
| tip_amount | Integer | Optional | Tip portion of amount. |
| currency | String | Always | ISO 4217 numeric, as a string. |
| response_status | TransactionStatus | Always | The outcome. See §25. |
| response_code | String | Optional | Acquirer response code. |
| response_message | String | Always | Human-readable outcome. |
| incoming | Incoming | Always | DEVICE or API. |
| auth_code | String | Optional | Authorization code. Needed for ECOPAYNET follow-ups. |
| refund_id | String | Optional | Required to refund or reverse this transaction. Store it. |
| parent_transaction_id | String | Optional | Set on operations that reference an earlier one. |
| aid | String | Optional | EMV application identifier. |
| brand | String | Optional | Card scheme. |
| pan | String | Optional | Masked card number. |
| panExpiryDate | String | Optional | Masked expiry. |
| card_input_type | CardInputType | Optional | How the card was read. See §25. |
| cvm_type_list | String | Optional | Cardholder verification method. |
| reversal | Boolean | Optional | Whether this transaction was reversed. |
| date_terminal | Date | Optional | Terminal clock. |
| date_server | Date | Optional | Backend clock. Prefer this when they disagree. |
| emv_response | Object | Optional | EMV detail. |
| terminal_acquirer | String | Optional | Acquirer terminal ID. |
| merchant_acquirer | String | Optional | Acquirer merchant ID. |
| terminal_issuer | String | Optional | Issuer terminal ID. |
| merchant_issuer | String | Optional | Issuer merchant ID. |
| card_token | String | Optional | Card token from a card verify. |
| additional_token | String | Optional | Token from a tokenized sale, on either operator route. |
| par | String | Optional | Payment account reference. |
| transaction_type | String | Optional | Scheme transaction type. |
| card_holder_message | String | Optional | Message shown to the cardholder. |
| invoice_number | String | Optional | Acquirer invoice number. |
| reference | String | Optional | The reference you sent. |
| customer_ext_id | String | Optional | The customer identifier you sent. |
24.1 Example — one euro, approved
{
"device": "352966108076163",
"transaction_id": "1876c5dd-3450-4f65-9ca4-872398aa61c6",
"stan": "cec60447-7467-4457-a542-40a791f11f02",
"operative": "AUTHORIZATION",
"amount": 100,
"currency": "978",
"response_status": "PAID",
"response_message": "success",
"incoming": "DEVICE",
"aid": "A0000000031010",
"auth_code": "478893",
"brand": "VISA",
"pan": "476173******0119",
"panExpiryDate": "****",
"card_input_type": "CLESS",
"cvm_type_list": "SIGNATURE",
"reversal": false,
"date_terminal": "Nov 12, 2024 04:03:44 PM",
"date_server": "Nov 12, 2024 04:03:48 PM",
"emv_response": { "smartCardScheme": 1, "authorisationResponseCode": "00" },
"terminal_acquirer": "TPPESNA004",
"merchant_acquirer": "TPPESNA004",
"refund_id": "1305910960162082816"
}
24.2 Example — one euro, declined
A decline is a normal response, not an error: it arrives as a well-formed result with a non-PAID status. Branch on response_status, never on whether the call returned.
{
"device": "352966108076163",
"transaction_id": "e1d7a4b1-2a8a-43e6-be62-001154f0d2f4",
"stan": "6162ab8a-92ab-4d7d-8393-26d3fd6b666c",
"operative": "AUTHORIZATION",
"amount": 100,
"currency": "978",
"response_status": "REFUSED",
"response_message": "KO - refused",
"incoming": "DEVICE",
"aid": "A0000000041010",
"auth_code": "327674",
"brand": "MASTERCARD",
"pan": "****************",
"card_input_type": "CLESS",
"cvm_type_list": "NO",
"reversal": false,
"terminal_acquirer": "M1TPNP0005",
"merchant_acquirer": "TPNP0005",
"refund_id": "128937286421796"
}
25. Enumerations
Operative
| Value | Meaning |
|---|---|
| AUTHORIZATION | Sale. |
| DEFERRED | Preauthorization. |
| CONFIRMATION | Capture of a preauthorization. |
| CANCELLATION | Release of a preauthorization. |
| DECREMENT | Partial release of a preauthorization. |
| DEFERRED_UPDATE | Adjustment to a preauthorization. |
| REFUND | Refund with the card present. |
| REFERRAL_REFUND | Refund without the card. |
| REVERSAL | Same-day void. |
| CARD_VERIFY | Card validation, no charge. |
| CFT | Credit to a card. |
| PAYOUT | Payout. |
| LIMITED_PAYMENT | Restricted payment. |
TransactionStatus
| Value | Meaning |
|---|---|
| PAID | Approved and settled locally. The only success value. |
| REFUSED | Declined by the issuer or acquirer. |
| PENDING | Under way; the outcome is not yet known. |
| STARTED | Begun on the terminal. |
| CANCELLED | Cancelled before completion. |
| EXPIRED | Timed out before completion. |
| ERROR | Failed for a technical reason. |
| DUPLICATED | Rejected as a duplicate. |
CHIP_ERROR is not a transaction status. Earlier revisions of this document listed it; it is not one of the values the SDK can return. A chip failure surfaces as ERROR with detail in response_message.
CardInputType
| Values | Meaning |
|---|---|
| CONTACT | Chip, inserted. |
| CLESS | Contactless. |
| CLESS_MAGSTRIPE | Contactless magnetic-stripe profile. |
| MAGNETIC | Swiped. |
| MAGNETIC_FALLBACK | Swiped after a chip failure. |
| CHIP | Chip. |
| MANUAL | Keyed by the operator. |
| KEY_ENTRY | Keyed entry. |
| NON_PRESENTIAL | Card not present. |
| UNKNOWN | Not reported by the terminal. |
Incoming
DEVICE — started on the terminal, which includes everything in this document. API — started by the backend.
26. Intent integration
26.1 Launching an operation
const val PAYMENT_RESULT_CODE = 1001
val stan = UUID.randomUUID()
val uri = "com.paynopain.sentinelservice://payment" +
"?amount=100¤cyCode=978&stan=$stan"
startActivityForResult(Intent(Intent.ACTION_VIEW, Uri.parse(uri)), PAYMENT_RESULT_CODE)
26.2 Reading the result
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != PAYMENT_RESULT_CODE) return
when (resultCode) {
RESULT_OK -> {
// Same payload as the HTTP and USB transports return.
val result = data?.getStringExtra("transactionResult")
}
RESULT_CANCELED -> {
val errorMessage = data?.getStringExtra("error_message")
}
}
}
26.3 Available intent hosts
Every URI takes the form com.paynopain.sentinelservice://<host>?<query>. Query parameter names match the JSON field names of the corresponding operation.
payment confirmation transaction
preauthorization cancellation transactionbystan
refund decrement healthcheck
referral cardverify sync
reversal cft
Operations absent from this list — list transactions, reports, close batch, reprint and identify — are reachable over HTTP and USB only.
26.4 Checking availability first
val installed = try {
packageManager.getPackageInfo("com.paylands.posms.sentinel", 0)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
Check the package name, not the intent scheme. They are deliberately different — see §2.
The information contained in this document is property of PAYNOPAIN SL and may not be copied, used or disclosed in whole or in part, except with the prior written consent of PAYNOPAIN SL.