DocumentationTerminal payments

Terminal payments

This guide walks you through integrating card payments on payment terminals into your cash register (POS) using the Qerko POS API. Your cash register sends the amount, the customer pays on the terminal, and your cash register gets the result with everything needed for the receipt.

The guide covers the whole process:

  1. Before you start – the API key and Pos-Id
  2. Sending requests – authentication and how responses work
  3. Getting the terminals
  4. Checking a terminal (optional)
  5. Payments
  6. Reversals – cancelling a payment in full
  7. Refunds – returning money (or a part of it) back to the card
  8. Closings – closing the terminal batch
  9. Errors when starting an operation
  10. Recommendations

The full reference of all methods and fields is in the API Reference under the Terminal payments section.

How it works

You don’t talk to the terminal directly. Every operation (payment, reversal, refund, closing) works the same way:

  1. Your cash register starts the operation with a create-terminal-* method. The method returns right away. The operation then runs on the terminal.
  2. Qerko drives the operation to completion in the background: it follows the terminal, gets the result, confirms it and releases the terminal.
  3. Your cash register polls the operation’s state with the matching get-terminal-*-status method until the state is final.

Each operation is identified by a transactionId that your cash register generates (a UUID). You use the same transactionId to poll the operation’s state.

Before you start

API key

Terminal payments use a dedicated API key with the Terminal payments role. It is a different key from the standard key your POS uses for the other Qerko services.

  • How to get it: Qerko issues the key on request, and it cannot be generated in Restaurant Admin. To request one, contact Qerko.
  • Which company: the key belongs to a company (legal entity), not to a single restaurant. It gives access to the terminals of all active restaurants (locations) of that company.
  • Where it shows up: the merchant can see the key in Restaurant Admin under API keys, with the role Terminal payments. The merchant can also delete it there, and a deleted key stops working right away.
  • Keep it secret: treat the key as a secret. Store it only in your cash register’s configuration and never show it to customers.

Pos-Id

Every request also carries the Pos-Id of your cash register system, as for the rest of the POS API.

In the sandbox environment, you can use any UUID as the Pos-Id.

Terminals

A terminal can be used only when the integration is enabled for it and it runs in the integrated mode. Qerko takes care of both when setting the terminal up. Your cash register finds out which terminals are ready from /get-terminals (enabled: true).

Environments

EnvironmentBase URL
Sandboxhttps://sandbox.qerko.com/api/v4/pos
Productionhttps://qerko.com/api/v4/pos

Sending requests

All methods are POST requests with a JSON body (an empty object {} when the method takes no data). Every request carries two headers:

Compiling...

Responses

  • Every business outcome is returned as HTTP 200. Outcomes are told apart by the type field, for example started, unknownTerminal or terminalBusy. Always switch on type. Don’t rely on the HTTP status alone.
  • The state of an operation is in its status field: pending while it runs, then a final status (approved / completed, failed or unknown).
  • 401 means a missing or invalid Pos-Id or API key, or a key without the Terminal payments role.
  • 400 means the request data is invalid (for example a missing field or an amount with more than 2 decimal places).

Data formats

  • Amounts are decimal strings with at most 2 decimal places, for example "249.90".
  • Currencies are ISO 4217 codes: CZK, EUR, HUF, PLN, USD.
  • Dates are ISO 8601 strings in UTC, for example "2026-10-14T09:15:22.123Z".
  • IDs (transactionId, …) are UUIDs.

Step 1: Get the terminals

POST /get-terminals returns the company the API key belongs to, its active locations (restaurants) and their terminals.

Request

{}

Response

{
    "legalEntity": {
        "id": "8d0d5c2e-6f4b-4b8e-9c55-1f1c1c0a7b21",
        "name": "Bistro s.r.o.",
        "identificationNumber": "12345678",
        "country": "CZ"
    },
    "locations": [
        {
            "id": "3a7e2f4c-1b2d-4e5f-8a9b-0c1d2e3f4a5b",
            "name": "Bistro Karlín",
            "address": {
                "street": "Křižíkova",
                "descriptiveNumber": "12",
                "city": "Praha",
                "zipCode": "18600",
                "country": "CZ"
            },
            "terminals": [
                {
                    "id": "A1B2C3D4",
                    "tid": "A1B2C3D4",
                    "platform": "physical",
                    "enabled": true
                }
            ]
        }
    ]
}
FieldMeaning
idTerminal identifier. Send it as terminalId to the other methods. Treat it as an opaque value: today it equals tid, but it may change to a different identifier later. Don’t parse it or derive it from tid.
tidTerminal ID as shown on the terminal and on the receipts, for information only.
platformphysical for a payment terminal, softPos for a tap-to-pay app on an Android phone.
enabledWhether the terminal can be used for terminal payments. Only terminals with enabled: true work with the other methods.

Typically the cash register lets the staff pick a terminal from this list once, during setup, and stores its id. Call /get-terminals again when the setup changes.

Step 2 (optional): Check the terminal

POST /get-terminal-status checks that the terminal is connected. Optionally, it also checks that the terminal reaches the authorization server. Use it, for example, in the cash register’s setup screen to test the connection.

Request

{
    "terminalId": "A1B2C3D4",
    "checkAuthorizationServer": true
}

Response

{
    "type": "status",
    "terminalId": "A1B2C3D4",
    "terminalDateTime": "2026-10-14T09:15:22.000Z",
    "authorizationServerConnection": "ok",
    "authorizationServerMessage": "Connection OK"
}
  • authorizationServerConnection: notChecked (when checkAuthorizationServer is false), ok or error.
  • Other outcomes: unknownTerminal, terminalNotActivated, terminalBusy, terminalNotConnected, upstreamTimeout and upstreamError. See Errors when starting an operation.
  • During an operation: while a payment or another operation runs on the terminal, the check returns terminalBusy.

Step 3: Payments

Starting a payment

POST /create-terminal-payment starts a payment on the terminal. The customer then pays on the terminal.

Request

Compiling...
FieldRequiredMeaning
terminalIdyesid of the terminal from /get-terminals
transactionIdyesA new UUID generated by your cash register for this payment. It must be unique. A reused value ends with duplicateTransaction.
amountyesTotal amount, including tipAmount
currencyyesCurrency of the payment
tipAmountnoTip included in amount
referenceIdnoYour reference, for example the bill number, at most 20 characters. It doesn’t have to be unique, so a bill split into several payments can share it.

Response: the payment was started.

{
    "type": "started",
    "payment": {
        "status": "pending",
        "step": "connecting",
        "transactionId": "0f8b7c1e-5a4d-4c3b-9e2f-7a6b5c4d3e2f",
        "referenceId": "BILL-2026-001234",
        "amount": "270.00",
        "tipAmount": "20.00",
        "currency": "CZK",
        "createdAt": "2026-10-14T09:15:22.123Z"
    }
}

If the payment did not start, type says why:

  • unknownTerminal: the terminal doesn’t exist, isn’t enabled, or belongs to another company.
  • notStarted: the payment processor refused the payment, for example because of an invalid amount or an unsupported currency.
  • Other errors: see Errors when starting an operation.

Following the payment

POST /get-terminal-payment-status returns the current state of the payment. It reads the state stored by Qerko and does not contact the terminal, so you can call it as often as you need. We recommend polling once per second until the status is final.

Request

{
    "transactionId": "0f8b7c1e-5a4d-4c3b-9e2f-7a6b5c4d3e2f"
}

Response: type is found with the payment, or unknownTransaction when no payment with this transactionId was started.

While the payment runs (status: pending), step tells you what is happening on the terminal. You can show it to the staff:

stepMeaning
connectingThe terminal is connecting to the payment processor
waitingForCardWaiting for the customer to tap or insert the card
waitingForPinWaiting for the customer to enter the PIN
waitingForCustomerWaiting for another customer input on the terminal
authorizingThe payment is being authorized
finishingThe result is known and the payment is being finished

Payment results

statusMeaningWhat the cash register does
pendingThe payment is runningKeep polling
approvedThe card was charged and the payment is confirmedMark the bill as paid and print the receipt
failedThe card was not chargedTell the staff and let them try again or pick another payment method
unknownThe result could not be determinedThe staff must check the payment on the terminal (in its transaction history) before closing the bill

Approved payment

{
    "type": "found",
    "payment": {
        "status": "approved",
        "transactionId": "0f8b7c1e-5a4d-4c3b-9e2f-7a6b5c4d3e2f",
        "referenceId": "BILL-2026-001234",
        "amount": "270.00",
        "tipAmount": "20.00",
        "currency": "CZK",
        "createdAt": "2026-10-14T09:15:22.123Z",
        "finishedAt": "2026-10-14T09:15:41.456Z",
        "chargedAmount": "270.00",
        "chargedTipAmount": "20.00",
        "authorizationCode": "123456",
        "card": {
            "brand": "VISA",
            "maskedPan": "************8902",
            "panSequenceNumber": "01",
            "entryMode": "contactless",
            "aid": "A0000000031010",
            "applicationLabel": "VISA CREDIT"
        },
        "cardholderVerification": ["none"],
        "receipt": {
            "merchantId": "000123456",
            "terminalId": "A1B2C3D4",
            "sequenceNumber": "000042",
            "terminalDateTime": "2026-10-14T09:15:40.000Z",
            "refundId": "R-7F3A9C21"
        },
        "reversalTransactionId": null
    }
}
FieldMeaning
chargedAmount, chargedTipAmountThe amount actually charged, including the tip. It can differ from amount when the customer added a tip on the terminal. Use these values for the bill.
authorizationCodeAuthorization code for the receipt
cardCard brand, masked card number, how the card was read (entryMode: contactless, chip or magstripe) and EMV data for the receipt
cardholderVerificationHow the cardholder was verified: none (for example a low-value contactless payment), signature, pinOnline, pinOffline or consumerDevice
receiptData for the receipt: merchant ID and terminal ID at the acquirer, sequence number and the time on the terminal
receipt.refundIdIdentifier you need for a later refund. Store it with the payment.
reversalTransactionIdtransactionId of the reversal when the payment was later reversed, otherwise null

The payment is confirmed automatically, so your cash register doesn’t need to confirm it. To undo an approved payment, use a reversal or a refund.

Failed payment

{
    "type": "found",
    "payment": {
        "status": "failed",
        "reason": "declined",
        "transactionId": "0f8b7c1e-5a4d-4c3b-9e2f-7a6b5c4d3e2f",
        "referenceId": "BILL-2026-001234",
        "amount": "270.00",
        "tipAmount": "20.00",
        "currency": "CZK",
        "createdAt": "2026-10-14T09:15:22.123Z",
        "finishedAt": "2026-10-14T09:15:35.001Z"
    }
}
reasonMeaning
declinedDeclined by the card issuer or the terminal
cancelledCancelled on the terminal or by the customer
reversedAuthorized, but reversed by the payment processor (for example because it could not be confirmed)

In every case the card was not charged, so you can start a new payment with a new transactionId.

Step 4: Reversals

A reversal cancels an approved payment in full. It runs on the same terminal that made the payment, and the customer’s card is not needed. Use it, for example, when a payment was made by mistake.

POST /create-terminal-reversal

Request

{
    "transactionId": "5c2d1e0f-9a8b-4c7d-8e6f-1a2b3c4d5e6f",
    "originalTransactionId": "0f8b7c1e-5a4d-4c3b-9e2f-7a6b5c4d3e2f"
}
FieldMeaning
transactionIdA new UUID for the reversal
originalTransactionIdtransactionId of the payment to reverse

Response: type: started with the reversal (status: pending). If the reversal did not start, type says why:

typeMeaning
originalTransactionNotFoundNo payment with originalTransactionId exists for this company
originalTransactionNotReversibleThe payment isn’t approved, so there is nothing to reverse
alreadyReversedThe payment was already reversed, or its reversal is still running
terminalUnavailableThe terminal of the payment is no longer active or enabled
notStartedThe payment processor refused the reversal, for example because the payment can no longer be reversed
otherSee Errors when starting an operation

Poll POST /get-terminal-reversal-status with the reversal’s transactionId:

{
    "type": "found",
    "reversal": {
        "status": "approved",
        "transactionId": "5c2d1e0f-9a8b-4c7d-8e6f-1a2b3c4d5e6f",
        "originalTransactionId": "0f8b7c1e-5a4d-4c3b-9e2f-7a6b5c4d3e2f",
        "amount": "270.00",
        "currency": "CZK",
        "createdAt": "2026-10-14T09:20:02.000Z",
        "finishedAt": "2026-10-14T09:20:06.000Z",
        "card": {
            "brand": "VISA",
            "maskedPan": "************8902",
            "panSequenceNumber": "01",
            "entryMode": "contactless",
            "aid": "A0000000031010"
        },
        "receipt": {
            "merchantId": "000123456",
            "terminalId": "A1B2C3D4"
        }
    }
}
statusMeaning
pendingThe reversal is running
approvedThe payment was reversed and the charged amount (amount) was returned to the card. finishedAt is the time of the reversal for the receipt. The authorization code and sequence number of the original payment are in /get-terminal-payment-status.
failedThe reversal did not go through (reason: declined or cancelled), so the payment stays charged
unknownThe result could not be determined and has to be checked on the terminal

After an approved reversal, /get-terminal-payment-status of the payment returns the reversal’s transactionId in reversalTransactionId.

Step 5: Refunds

A refund returns money (the full amount or a part of it) back to the card of an earlier payment.

  • How the payment is identified: by its refundId from the payment receipt (receipt.refundId of the approved payment).
  • Which terminal: the card is not presented, so the refund can run on any enabled terminal of the company.

POST /create-terminal-refund

Request

{
    "terminalId": "A1B2C3D4",
    "transactionId": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
    "refundId": "R-7F3A9C21",
    "amount": "50.00",
    "currency": "CZK",
    "referenceId": "CN-2026-000017"
}
FieldRequiredMeaning
terminalIdyesTerminal to run the refund on
transactionIdyesA new UUID for the refund
refundIdyesrefundId from the receipt of the payment to refund
amountyesAmount to refund
currencyyesCurrency of the refund
referenceIdnoYour reference, for example a credit note number, at most 20 characters
⚠️

Qerko doesn’t check the refund against the original payment (amount, currency, earlier refunds or a reversal). That is up to the payment processor. An unknown refundId isn’t rejected right away: the refund starts and then ends as failed with reason: declined.

Response: type: started with the refund (status: pending), or unknownTerminal, notStarted or one of the start errors.

Poll POST /get-terminal-refund-status with the refund’s transactionId:

{
    "type": "found",
    "refund": {
        "status": "approved",
        "transactionId": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
        "referenceId": "CN-2026-000017",
        "amount": "50.00",
        "currency": "CZK",
        "createdAt": "2026-10-15T11:02:10.000Z",
        "finishedAt": "2026-10-15T11:02:18.000Z",
        "authorizationCode": "654321",
        "card": {
            "brand": "VISA",
            "maskedPan": "************8902"
        },
        "receipt": {
            "merchantId": "000123456",
            "terminalId": "A1B2C3D4",
            "sequenceNumber": "000057",
            "terminalDateTime": "2026-10-15T11:02:17.000Z"
        }
    }
}
statusMeaning
pendingThe refund is running
approvedThe amount was returned to the card, and the response contains the data for the refund receipt
failedNothing was returned (reason: declined or cancelled)
unknownThe result could not be determined and has to be checked on the terminal

Step 6: Closings

A closing (end-of-day, batch close) closes the terminal’s current batch and returns its totals. Run it, for example, at the end of the day for each terminal.

POST /create-terminal-closing

Request

{
    "terminalId": "A1B2C3D4",
    "transactionId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e"
}

Response: type: started with the closing (status: pending), or unknownTerminal, notStarted or one of the start errors.

Poll POST /get-terminal-closing-status with the closing’s transactionId:

{
    "type": "found",
    "closing": {
        "status": "completed",
        "transactionId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
        "terminalId": "A1B2C3D4",
        "createdAt": "2026-10-14T22:00:01.000Z",
        "finishedAt": "2026-10-14T22:00:09.000Z",
        "batchNumber": 128,
        "shiftNumber": 1,
        "terminalDateTime": "2026-10-14T22:00:08.000Z",
        "payments": { "count": 57, "amount": "18450.00" },
        "refunds": { "count": 1, "amount": "50.00" },
        "adjustments": { "count": 0, "amount": "0.00" }
    }
}
statusMeaning
pendingThe closing is running
completedThe batch was closed. The response contains the batch number, the shift number and the totals (count and sum) of payments, refunds and adjustments in the terminal’s currency.
failedThe batch was not closed (reason: declined or cancelled)
unknownThe result could not be determined and has to be checked on the terminal

Errors when starting an operation

All create-terminal-* methods (and /get-terminal-status) can end with one of the following outcomes instead of started. Each has a message with a short description.

typeMeaningWhat to do
unknownTerminalThe terminal doesn’t exist, isn’t enabled, or belongs to another companyCheck the terminal in /get-terminals
terminalNotActivatedThe terminal isn’t activated at the payment processorThe terminal has to be set up, and retrying doesn’t help. Contact Qerko support.
terminalBusyThe terminal is used by another cash register, or an earlier operation on it is still runningWait for the running operation to finish, then start again
terminalNotConnectedThe terminal is offline: switched off, no connection, or its payment app isn’t runningAsk the staff to check the terminal, then retry
busyThe terminal is processing another operation right nowRetry in a few seconds
duplicateTransactionThis transactionId was already usedGenerate a new transactionId. If you were retrying, check the state of the original operation first (see Recommendations).
upstreamTimeoutThe payment processor didn’t answer in time, and it isn’t known whether the operation startedCheck the terminal before retrying. A started payment or refund that nobody confirms is reversed automatically.
upstreamErrorThe request to the payment processor failedRetry later
notStartedThe payment processor refused to start the operationSee cause.code and cause.message for the processor’s reason

Recommendations

  • Generate a new transactionId for every operation, and save it in your cash register before you call the create-terminal-* method.
  • Don’t blindly repeat a start when its result is unclear. If the call fails or times out on your side (network error, …), call the matching get-terminal-*-status with the same transactionId first:
    • found: the operation exists, so continue polling it.
    • unknownTransaction: the operation was not started, so you can start it again.
  • Poll the status once per second while status is pending, and stop when the status is final. Show step to the staff so they know what is happening on the terminal.
  • Only one operation runs on a terminal at a time. Wait until the previous payment, reversal, refund or closing reaches its final status before you start another one on the same terminal.
  • Your cash register doesn’t have to stay online. Qerko finishes a started operation even if your cash register stops polling or restarts. After a restart, look up unfinished operations by their saved transactionId.
  • Use chargedAmount for the bill, not the requested amount, because the customer may have added a tip on the terminal.
  • Store receipt.refundId of every approved payment, because it is needed for a later refund.
  • Handle unknown carefully. It is rare, but when it happens, the staff must check the result on the terminal (in its transaction history) before they treat the bill as paid or unpaid.

Methods at a glance

MethodPurpose
/get-terminalsCompany, its locations and terminals
/get-terminal-statusIs the terminal connected and does it reach the authorization server?
/create-terminal-paymentStart a payment
/get-terminal-payment-statusState and result of a payment
/create-terminal-reversalReverse an approved payment in full
/get-terminal-reversal-statusState and result of a reversal
/create-terminal-refundRefund (a part of) a payment back to its card
/get-terminal-refund-statusState and result of a refund
/create-terminal-closingClose the terminal batch
/get-terminal-closing-statusState and totals of a closing

Need help? Contact techsupport@qerko.com for assistance.