Cloud Accept API
BetaStart in-store payments on a paired Tapaya device from your ECR or POS backend.
The Cloud Accept API lets your electronic cash register (ECR) or POS backend start a payment on a Tapaya device over the cloud. Your system never talks to the device directly: it sends a payment request to Tapaya, Tapaya notifies the paired device, and the device shows the payment screen to the customer. Progress comes back to you as webhooks, or you poll for it.
Choose How to Pair Devices
Every device you send payments to needs an externalDeviceId: the name your system uses for it, such as
till-01. Assign it in one of two ways:
From your app
Your app assigns the externalDeviceId through the Accept SDK on the device.
QR code from the platform
The merchant shows a QR code in the Tapaya Platform and scans it with the merchant app.
Before You Start
- Get your Server Secret Token from the API Keys page. You exchange it for a merchant-scoped access token below (see Platform API authentication).
- Register the merchant and keep its
merchantToken. See Register a New Merchant. - Prepare each device with the Tapaya app signed in for that merchant, so it can be paired and receive requests.
One token, one merchant
Every Cloud Accept API call is scoped to exactly one merchant. To serve several merchants, exchange a separate access token for each one.
Pair a Device
Pairing assigns an externalDeviceId to a Tapaya device, so you can find it when listing devices
and target it when starting a payment.
Assign from your app
Your application calls an Accept SDK function on the device to assign its externalDeviceId. Use
the same identifier in every Cloud Accept API call for that device.
Scan a QR code
The merchant opens the pairing flow in the Tapaya Platform, which shows a QR code for the
externalDeviceId to assign. The merchant scans it with the Tapaya merchant app on the target
device, and the app completes pairing.
Exchange an Access Token
Exchange your Server Secret Token and the merchant's merchantToken for an access token with
POST /ecr/auth. merchantToken is sent only here; every later call sends just
Authorization: Bearer <accessToken>.
curl -X 'POST' 'https://api.tapaya.com/ecr/auth' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer REPLACE_ME' \
-d '{
"merchantToken": "unique_merchant_id_from_your_db"
}'{
"accessToken": "...",
"tokenType": "Bearer",
"expiresAt": "2026-01-13T16:30:59Z"
}| Code | Description |
|---|---|
401 | Invalid or expired API token. |
403 | Access to the merchant is not permitted. |
Renew before expiresAt. Call POST /ecr/auth/refresh with the current, still-valid
accessToken in the Authorization header. It returns the same body with a new token; store it
and use it from then on.
curl -X 'POST' 'https://api.tapaya.com/ecr/auth/refresh' \
-H 'Authorization: Bearer ACCESS_TOKEN'| Code | Description |
|---|---|
401 | The access token is invalid, expired, or revoked. Exchange a new one with POST /ecr/auth. |
403 | You no longer have access to the merchant. No new token is issued. |
Find a Ready Device
List the merchant's devices with GET /ecr/devices. Add ?connected=true to leave out
disconnected devices, or fetch one device with GET /ecr/devices/{externalDeviceId}.
curl 'https://api.tapaya.com/ecr/devices/till-01' \
-H 'Authorization: Bearer ACCESS_TOKEN'{
"externalDeviceId": "till-01",
"name": "Front counter",
"connected": true,
"availability": "ready",
"lastSeenAt": "2026-01-13T16:30:59Z",
"updatedAt": "2026-01-13T16:30:59Z"
}Send payments only to a device whose availability is ready. The list is a snapshot:
availability is checked again when you submit, so a ready device can still turn a request
down. See Device availability.
Submit a Payment Request
Send the amount to a device with POST /ecr/payment-requests. Tapaya stores the request and
notifies the device, which shows the payment screen.
curl -X 'POST' 'https://api.tapaya.com/ecr/payment-requests' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Idempotency-Key: order-1042-attempt-1' \
-d '{
"externalDeviceId": "till-01",
"amount": 12500,
"tipAmount": 500,
"currency": "CZK"
}'Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
externalDeviceId | string | ✓ | The paired device that should show the payment. |
amount | integer | ✓ | Amount in the currency's smallest unit (12500 is 125.00 Kč). |
tipAmount | integer | Tip in the currency's smallest unit. | |
currency | string | ✓ | ISO 4217 currency code. |
paymentToken | string | An existing payment to attach the request to. |
{
"requestId": "...",
"status": "pending",
"paymentToken": null,
"expiresAt": "2026-01-13T16:30:59Z"
}202 is not a payment result
202 Accepted means Tapaya has stored the request. It does not mean the device has seen it
or that the customer has paid. Anything that fails later reaches you as a status change.
Make retries safe. The Idempotency-Key header is required. Reusing a key with the same body
returns the original request instead of starting a second payment; reusing it with a different
body returns 409 idempotency_conflict. Tie the key to the sale and attempt, as in
order-1042-attempt-1.
Track the Result
Follow the request through its statuses with webhooks, or poll
GET /ecr/payment-requests/{requestId}. Once it reaches payment_created, the response carries
the paymentToken, and payment.updated events report the payment's own progress.
To receive webhooks, register your HTTPS endpoint with POST /ecr/webhook-subscriptions, or set the
callback URL and event types for the merchant in the Tapaya Platform.
curl -X 'POST' 'https://api.tapaya.com/ecr/webhook-subscriptions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-d '{
"url": "https://integrator.example.com/webhooks/tapaya",
"events": ["payment_request.updated", "payment.updated"]
}'{
"subscriptionId": "...",
"url": "https://integrator.example.com/webhooks/tapaya",
"events": ["payment_request.updated", "payment.updated"],
"signingSecret": "..."
}Store the signing secret
signingSecret is returned only when the subscription is created. Store it securely; you
need it to verify the signature of incoming webhooks.
A subscription belongs to the integrator and merchant, not to an access token, so it survives
token renewal. Answer each event with a 2xx once you've accepted it.
Verify before you close the sale
Webhooks are notifications, not proof of payment, and one can arrive late or not at all. Never close a sale on a webhook alone:
- When a webhook arrives, confirm the final result through the API, for example with Retrieve Payment Details.
- When no webhook arrives within a timeout that suits your checkout flow, and no later
than the request's
expiresAt, callGET /ecr/payment-requests/{requestId}to read its current status, then confirm the payment through the API as above.
Reference
Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST | /ecr/auth | Exchanges your API token and a merchantToken for an access token. |
POST | /ecr/auth/refresh | Renews a still-valid access token. |
GET | /ecr/devices | Lists the merchant's devices. Optional connected=true filter. |
GET | /ecr/devices/{externalDeviceId} | Returns one device and its current availability. |
POST | /ecr/payment-requests | Stores a payment request and notifies the device. Requires Idempotency-Key. |
GET | /ecr/payment-requests/{requestId} | Returns the request status, any rejection reason, and the paymentToken. |
POST | /ecr/webhook-subscriptions | Registers an HTTPS URL and event types. Returns the signing secret once. |
GET | /ecr/webhook-subscriptions | Lists your subscriptions for the merchant, without signing secrets. |
DELETE | /ecr/webhook-subscriptions/{subscriptionId} | Removes a subscription. |
Device availability
Both device endpoints return externalDeviceId, name, connected, availability, lastSeenAt, and
updatedAt.
| Value | Description |
|---|---|
ready | Connected, and able to receive and display a request. |
busy | Processing a payment, or holding an active request. |
unavailable | Disconnected, without a valid session, or unable to show the payment screen. |
Request statuses
| Status | Description |
|---|---|
pending | Stored and waiting for the device to accept it. |
acknowledged | The device has received it; the payment may not exist yet. |
payment_created | A payment and its paymentToken exist. Track further progress on the payment. |
rejected | The device or Tapaya turned the request down. |
Errors
| Code | Reason | Description |
|---|---|---|
403 / 404 | Object unavailable | You lack permission, or the object does not exist. |
409 | device_busy | The device already has an active request or payment. |
409 | device_unavailable | The device is connected but not ready. |
409 | idempotency_conflict | The key was already used with a different body. |
503 | device_offline | The device is not connected. |
Amount and currency validation errors follow the same conventions as the rest of the Platform API.
Webhook events
| Event | Sent when | data contents |
|---|---|---|
device.connected | A device opens a new live connection. | externalDeviceId, connected, availability, lastSeenAt |
device.disconnected | The connection closes or the liveness check times out. | externalDeviceId, connected, availability, lastSeenAt |
device.availability_changed | Availability moves between ready, busy, and unavailable. | externalDeviceId, availability, optional reason |
payment_request.updated | The request is stored, acknowledged, rejected, expires, or gets a payment. | requestId, externalDeviceId, status, paymentToken, reference, reason, expiresAt |
payment.updated | The payment's status changes. | paymentToken, requestId, externalDeviceId, status, reference, amount, currency, optional receiptUrl |
Every event arrives as an HTTP POST in the same envelope:
{
"eventId": "...",
"type": "payment_request.updated",
"occurredAt": "2026-01-13T16:30:59Z",
"data": {
"requestId": "...",
"externalDeviceId": "till-01",
"status": "acknowledged",
"paymentToken": null,
"reference": "ORDER-1042",
"reason": null,
"expiresAt": "2026-01-13T16:30:59Z"
}
}