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"
}'
| Field | Required | Description |
|---|---|---|
merchantExternalId | yes | Your business identifier. |
storeExternalId | yes | The store the payment is taken at. |
terminalExternalId | yes | The device at that store. |
amount | yes | Decimal amount in the payment's currency, greater than zero. |
currency | yes | ISO 4217 code, for example SGD. |
reference | recommended | Your own reference for the bill. Use it to match the outcome back to your records. |
sequenceNumber | optional | Position of this tender when one bill is split across several payments. |
requestedModes | optional | Restrict what the terminal offers, for example ["CARD"]. Omit to allow every mode the device supports. |
terminalTimeoutMinutes | optional | How long the device waits for the customer. |
cashierId, customerMobile, customerEmail | optional | Passed through for the receipt and your records. |
The response is the payment, waiting at the terminal:
{
"id": 90210,
"pspReference": "BNDL7K2P9QW1ABCD",
"status": "AWAITING_TERMINAL",
"amount": 100.00,
"currency": "SGD"
}
id— the payment id. Use it to check status.pspReference— Boundless's stable reference for the payment.status—AWAITING_TERMINALmeans the bill is on the device and no money has moved yet.
The customer now taps their card at the terminal.
Step 2 — Follow it to the outcome
You learn the result two ways. Use webhooks as your primary path; use status polling as a fallback and for an on-demand "check status" button.
By webhook (recommended)
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/90210 \
-H "X-API-Key: bndl_live_..."
Refresh from the terminal — asks the acquirer what happened at the device and applies the answer before responding. Use it 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/90210/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": 90210,
"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.
Outcomes
| Status | Meaning |
|---|---|
AWAITING_TERMINAL | On the device, waiting for the customer. Not yet paid. |
CAPTURED | Paid. Funds are captured. |
AUTHORIZED | Approved and held, awaiting completion. Applies only to terminals configured for a two-step authorize-then-complete flow. |
REFUSED | Declined at the terminal. |
CANCELLED | The bill was cancelled before the customer paid. |
EXPIRED | Nobody completed the bill in time; Boundless closed it automatically. |
ERROR, FAILED | A processing problem prevented a decision. Safe to retry with the same reference. |
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
referenceis 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 samereferencewith 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.
ERRORandFAILEDare final, so re-creating with the samereferencestarts 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
referenceand give each tender its ownsequenceNumber(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
REFUSEDand the terminal provider's explanation attached. Complete or cancel the open bill, then create again. Always branch on the returnedstatus, never on the HTTP code alone.
Good practice
- Match on your
reference. Every outcome carries the payment back to the bill you started, so set areferenceyou 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
201on the start request means the bill reached the device, not that the customer paid. The payment is only paid atCAPTURED. - 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.
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/90210/cancel \
-H "X-API-Key: bndl_live_..."
The response is the payment in its resulting state — normally CANCELLED.
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.
You can only cancel a payment that is still waiting at the terminal. Cancelling one that already reached a final state returns that state unchanged.
Unresponsive terminals
Forcing a bill off a device that cannot be reached is a separate, operator-only action, because it interrupts the terminal itself rather than simply withdrawing a bill. Contact Boundless if a terminal is stuck with a bill you cannot cancel normally.