This page describes how to make an in-person payment with Adyen's Terminal API: you send a payment request, the terminal interacts with the shopper, and you receive the result. For how your POS app connects, see Architecture Overview.
At a glance
Your POS app sends a Terminal API payment request, the request reaches the terminal, the shopper taps, inserts, or swipes their card and completes any prompt such as PIN, and you receive the result. The authoritative final outcome is also confirmed by a webhook. See Webhooks.
The request
A Terminal API payment is a SaleToPOIRequest that wraps a message header and a PaymentRequest.
The message header (SaleToPOIRequest.MessageHeader) identifies the transaction and the terminal:
ProtocolVersion:3.0MessageClass:ServiceMessageCategory:PaymentMessageType:RequestServiceID: a unique ID for this request from your POS appSaleID: your identifier for the POS system component sending the requestPOIID: the unique ID of the target terminal, in the format[device model]-[serial number]
The PaymentRequest carries the transaction reference and amount:
SaleData.SaleTransactionID: an object with aTransactionID(your reference for the payment, recommended unique per payment, shown as the merchant reference in your Customer Area) and aTimeStampin UTC formatPaymentTransaction.AmountsReq: an object withCurrency(three-letter currency code) andRequestedAmount(the amount in number format with a decimal point, for example10.99)
POST {terminal-api-endpoint}/sync // cloud, synchronous result
{
"SaleToPOIRequest": {
"MessageHeader": {
"ProtocolVersion": "3.0",
"MessageClass": "Service",
"MessageCategory": "Payment",
"MessageType": "Request",
"ServiceID": "0421", // unique per request
"SaleID": "POS-Register-1",
"POIID": "MODEL-123456789" // [device model]-[serial number]
},
"PaymentRequest": {
"SaleData": {
"SaleTransactionID": {
"TransactionID": "ORDER-12345", // unique per payment
"TimeStamp": "2026-06-12T10:00:00.000Z" // UTC
}
},
"PaymentTransaction": {
"AmountsReq": { "Currency": "USD", "RequestedAmount": 10.99 }
}
}
}
}
The exact structure above is illustrative. Confirm field-level details against Adyen's Terminal API documentation before building. See Adyen's Make a payment with the Terminal API guide.
The response
You receive a SaleToPOIResponse containing a PaymentResponse. Check:
POIData.POITransactionID.TransactionID: the unique transaction identifier for the payment, in the formattenderReference.pspReference. Save it: you need it later for lookups, reconciliation, refunds, or manual capture.Response.Result:Successfor an approved payment,Failurefor a declined payment, orPartialfor a partial authorization.Response.ErrorCondition: present when the result isFailure, indicating why the payment failed.
// PaymentResponse (abbreviated)
{
"POIData": { "POITransactionID": { "TransactionID": "x5Hp...gQ.881..." } },
"Response": { "Result": "Success" }
}
Cloud and local endpoints
Local: send the request directly to the terminal, for example
https://{terminal-ip}:8443/nexo. You receive the result synchronously.Cloud: send the request to the Adyen platform. Use
/syncto receive the result synchronously or/asyncto receive it as an event notification. Test endpoints arehttps://terminal-api-test.adyen.com/syncand/async. Live uses a region-specific host, for examplehttps://terminal-api-live-us.adyen.com/sync.
Enable Terminal API in your Customer Area before you start.
Confirm with a webhook
If you do not receive a response, verify the transaction status rather than retrying blindly, to avoid duplicate payments. Update your records based on the webhook Adyen sends, not on the synchronous response alone.