Skip to main content

Accept a payment

Taking an in-store payment is two steps: start it, then follow it to its outcome.

Step 1 — Start the payment​

Send the amount to a store and terminal you have registered:

curl https://api-live.kcpboundless.com/api/in-store/payments \
-H "X-API-Key: bndl_live_..." \
-H "Content-Type: application/json" \
-d '{
"merchantExternalId": "your-merchant-id",
"storeExternalId": "your-store-id",
"terminalExternalId": "your-terminal-id",
"amount": 100.00,
"currency": "SGD",
"reference": "order-4821"
}'
FieldRequiredDescription
merchantExternalIdyesYour business identifier.
storeExternalIdyesThe store the payment is taken at.
terminalExternalIdyesThe device at that store.
amountyesDecimal amount in the payment's currency, greater than zero.
currencyyesISO 4217 code, for example SGD.
referencerecommendedYour own reference for the bill. Use it to match the outcome back to your records.
sequenceNumberoptionalPosition of this tender when one bill is split across several payments.
requestedModesoptionalRestrict what the terminal offers: ["CARD"] for cards, ["QR_CODE"] for PayNow and other QR tenders. Omit to allow every mode the device supports. See Choosing the tender.
terminalTimeoutMinutesoptionalHow long the device waits for the customer.
cashierId, customerMobile, customerEmailoptionalPassed through for the receipt and your records.

The response is the payment, waiting at the terminal:

{
"id": "01997b3e-6c31-7a42-9f10-4d2c8b5e7a01",
"pspReference": "BNDL7K2P9QW1ABCD",
"status": "AWAITING_TERMINAL",
"amount": 100.00,
"currency": "SGD"
}
  • id — the payment id. Use it to check status. It is an opaque string (a UUID); store it as text and pass it back unchanged. Do not parse it as a number.
  • pspReference — Boundless's stable reference for the payment.
  • status — AWAITING_TERMINAL means the bill is on the device and no money has moved yet.
Persist the id before showing the cashier anything

Store the payment id against the bill in your own database before the cashier sees the waiting screen. If your till crashes between the call and the write, you have lost the handle on a live payment.

The customer now taps their card at the terminal.

Step 2 — Follow it to the outcome​

You learn the result two ways, and a good integration uses both: poll sync-status for the live cashier screen, and subscribe to webhooks as your durable record of every outcome — see A recommended till flow below.

By webhook​

Register a webhook endpoint (see Webhooks) and Boundless will call it when the payment reaches a final state — captured, declined, cancelled. You do not have to poll.

By status call​

Two calls answer "where is this payment now", and they differ in cost:

Read the current state — a plain lookup of what Boundless already knows. Cheap; use this for routine polling:

curl https://api-live.kcpboundless.com/api/transactions/01997b3e-6c31-7a42-9f10-4d2c8b5e7a01 \
-H "X-API-Key: bndl_live_..."

Refresh from the terminal — asks the acquirer what happened at the device and applies the answer before responding. This is the call for the live cashier screen: while a cashier is watching the terminal, calling it on a short interval — every two to three seconds is reasonable — until the payment leaves AWAITING_TERMINAL gives the fastest honest answer. It is also the call for when you suspect Boundless's view is behind the device — for example the customer says they paid but the payment still reads as waiting:

curl -X POST https://api-live.kcpboundless.com/api/in-store/payments/01997b3e-6c31-7a42-9f10-4d2c8b5e7a01/sync-status \
-H "X-API-Key: bndl_live_..."

Either way the response is the payment, with its status advanced if the customer has finished:

{
"id": "01997b3e-6c31-7a42-9f10-4d2c8b5e7a01",
"status": "CAPTURED",
"amount": 100.00,
"currency": "SGD",
"cardBrand": "VISA",
"last4": "4242"
}

Both calls are safe to make repeatedly. If the payment is still open, you get AWAITING_TERMINAL again; once it is final, the state does not change on you. A sensible point of sale combines a webhook subscription with one sync-status behind its "check payment" button.

Every payment ends​

Boundless also runs a reconciliation sweep over in-store payments on its own, independently of anything your point of sale does. It picks up outcomes your till never asked about — including payments cancelled or completed at the device — and closes out bills that nobody decided.

The sweep runs on a cycle of a few minutes, and only considers a payment once its own terminal timeout has elapsed plus a short grace period. It is a safety net that guarantees every payment eventually reaches a final state, even if your till goes offline mid-sale.

It is not a mechanism to build a cashier experience on. The delay is measured in minutes, not seconds. When you need an answer now, call sync-status.

  1. Create the payment; show the cashier a waiting screen.
  2. Poll sync-status every few seconds while the screen is up.
  3. Resolve the screen as soon as the payment leaves AWAITING_TERMINAL.
  4. Give the cashier an explicit "check payment" button that calls sync-status — the answer to "the customer says it went through".
  5. Never leave a waiting screen with no exit. The background sweep guarantees the payment resolves eventually; your UI must be able to reach that resolution.
  6. Stop polling once the payment is in a final state. Continued polling of a decided payment does nothing and consumes your rate-limit budget.

Register a webhook alongside the polling: the webhook is your durable record of the outcome — delivered even if the till was offline when the payment resolved — while the polling is what keeps the cashier's screen honest.

Outcomes​

StatusMeaningWhat your till should do
CREATEDAccepted by the platform, not yet at the device.Keep waiting.
AWAITING_TERMINALOn the device, waiting for the customer. Not yet paid.Keep waiting. Show "follow the terminal" to the cashier.
CAPTUREDPaid. Funds are captured.Close the bill, print the slip.
AUTHORIZEDApproved and held, awaiting completion. Applies only to terminals configured for a two-step authorize-then-complete flow.Treat as paid for till purposes.
REFUSEDDeclined at the terminal.Offer another tender.
CANCELLEDWithdrawn before completion — by your point of sale, at the device itself, or reversed by a void.Close the bill as unpaid.
EXPIREDNobody completed the bill in time; Boundless closed it automatically.Close the bill as unpaid.
ERROR, FAILEDA processing problem prevented a decision.Safe to retry the create with the same reference.

Branch on status, not on the HTTP status alone — see one bill on the device at a time for the important case where a 201 Created carries a REFUSED payment.

Further statuses exist for post-sale processing. They are visible on a payment after the sale, but no till action depends on them.

Retries, duplicates, and split bills​

Point-of-sale software retries: networks drop, cashiers double-press, apps restart mid-request. The API is built for that.

  • One open payment per reference. While a payment carrying your reference is still open — waiting at the terminal or authorized — sending the same create request again returns that same payment rather than creating a duplicate. Retry a timed-out create with the same reference with confidence. Once the payment reaches a final state, the same reference may be used again for a genuinely new bill.
  • A failed payment is retry-safe. ERROR and FAILED are final, so re-creating with the same reference starts a fresh payment — the guarantee above ensures you never end up with two live ones.
  • Split bills. When one bill is settled by several tenders, keep the same reference and give each tender its own sequenceNumber (1, 2, 3…). Each reference-and-sequence pair gets its own one-open-payment guarantee.
  • One bill on the device at a time. A terminal shows one bill. Starting a second payment (different reference) while the first is still on the device does not return an HTTP error — the create responds normally with the new payment already REFUSED and the terminal provider's explanation attached. Complete or cancel the open bill, then create again. Always branch on the returned status, never on the HTTP code alone.

Retry policy​

ConditionRetry?
Network timeout, no responseYes — same reference
5xxYes, with backoff — same reference
429Yes, after Retry-After
4xx other than 429No. Fix the request.
200 / 201 with status ERROR or FAILEDYes — same reference
200 / 201 with status REFUSEDNo. The payment was decided. Offer another tender.

Good practice​

  • Match on your reference. Every outcome carries the payment back to the bill you started, so set a reference you can look up. The reference is also printed on the customer's payment slip, which lets store staff read a bill's reference straight off the paper when reconciling.
  • Do not assume from the start call. A 201 on the start request means the bill reached the device, not that the customer paid. The payment is only paid at CAPTURED.
  • A bill nobody completes is closed automatically. If a customer walks away, Boundless resolves the payment on its own after the terminal's timeout — you do not have to reconcile abandoned bills yourself.

Choosing the tender​

requestedModes decides what the terminal offers the customer:

ValueThe device offers
["CARD"]Card only
["QR_CODE"]PayNow and other QR tenders only
omitted, or several valuesEverything the device supports; the customer picks

Naming a single mode does more than narrow the screen — it tells Boundless what the payment is before the customer pays, so the payment is recorded and priced as that tender from the start:

curl -X POST https://api-live.kcpboundless.com/api/in-store/payments \
-H "X-API-Key: bndl_live_..." \
-H "Content-Type: application/json" \
-d '{
"merchantExternalId": "your-merchant-id",
"storeExternalId": "your-store-id",
"terminalExternalId": "your-terminal-id",
"amount": 18.50,
"currency": "SGD",
"reference": "order-4821",
"requestedModes": ["QR_CODE"]
}'

If you leave the choice to the customer, the payment starts as a card payment and Boundless corrects it to the tender the terminal reports once they have paid. Prefer naming the mode when your checkout already knows it — a PayNow button in your till should send ["QR_CODE"].

Not every terminal offers every tender

Which tenders a device can present is part of its setup with Boundless. Asking for a mode the device does not offer fails the request rather than silently falling back to another tender.

Cancelling an open bill​

If the customer changes their mind before paying, withdraw the bill from the device:

curl -X POST https://api-live.kcpboundless.com/api/in-store/payments/01997b3e-6c31-7a42-9f10-4d2c8b5e7a01/cancel \
-H "X-API-Key: bndl_live_..."

The response is the payment in its resulting state — normally CANCELLED.

A cancellation never overrides a completed payment

The cancel request and the customer's tap can race each other. If the customer completed the payment first, their payment stands and the response comes back CAPTURED, not CANCELLED. Always read the returned status rather than assuming the cancellation took effect.

That is the short version. The full picture — cancellations made at the device itself, undoing a sale the customer already paid (a void), and the rules that govern both — is in Reverse a payment.