Tapaya
In-Store AcceptanceIntegration GuideCloud Accept APIBeta

Cloud Accept API

Beta

Start 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:

Before You Start

  1. Get your Server Secret Token from the API Keys page. You exchange it for a merchant-scoped access token below (see Platform API authentication).
  2. Register the merchant and keep its merchantToken. See Register a New Merchant.
  3. 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"
    }'
200 OK
{
    "accessToken": "...",
    "tokenType": "Bearer",
    "expiresAt": "2026-01-13T16:30:59Z"
}
CodeDescription
401Invalid or expired API token.
403Access 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'
CodeDescription
401The access token is invalid, expired, or revoked. Exchange a new one with POST /ecr/auth.
403You 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'
200 OK
{
    "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:

FieldTypeRequiredDescription
externalDeviceIdstring✓The paired device that should show the payment.
amountinteger✓Amount in the currency's smallest unit (12500 is 125.00 Kč).
tipAmountintegerTip in the currency's smallest unit.
currencystring✓ISO 4217 currency code.
paymentTokenstringAn existing payment to attach the request to.
202 Accepted
{
    "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"]
    }'
201 Created
{
    "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, call GET /ecr/payment-requests/{requestId} to read its current status, then confirm the payment through the API as above.

Reference

Endpoints

MethodEndpointDescription
POST/ecr/authExchanges your API token and a merchantToken for an access token.
POST/ecr/auth/refreshRenews a still-valid access token.
GET/ecr/devicesLists the merchant's devices. Optional connected=true filter.
GET/ecr/devices/{externalDeviceId}Returns one device and its current availability.
POST/ecr/payment-requestsStores 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-subscriptionsRegisters an HTTPS URL and event types. Returns the signing secret once.
GET/ecr/webhook-subscriptionsLists 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.

ValueDescription
readyConnected, and able to receive and display a request.
busyProcessing a payment, or holding an active request.
unavailableDisconnected, without a valid session, or unable to show the payment screen.

Request statuses

StatusDescription
pendingStored and waiting for the device to accept it.
acknowledgedThe device has received it; the payment may not exist yet.
payment_createdA payment and its paymentToken exist. Track further progress on the payment.
rejectedThe device or Tapaya turned the request down.

Errors

CodeReasonDescription
403 / 404Object unavailableYou lack permission, or the object does not exist.
409device_busyThe device already has an active request or payment.
409device_unavailableThe device is connected but not ready.
409idempotency_conflictThe key was already used with a different body.
503device_offlineThe device is not connected.

Amount and currency validation errors follow the same conventions as the rest of the Platform API.

Webhook events

EventSent whendata contents
device.connectedA device opens a new live connection.externalDeviceId, connected, availability, lastSeenAt
device.disconnectedThe connection closes or the liveness check times out.externalDeviceId, connected, availability, lastSeenAt
device.availability_changedAvailability moves between ready, busy, and unavailable.externalDeviceId, availability, optional reason
payment_request.updatedThe request is stored, acknowledged, rejected, expires, or gets a payment.requestId, externalDeviceId, status, paymentToken, reference, reason, expiresAt
payment.updatedThe 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"
    }
}