Accept payments with Kushki One
The Payment API lets you process card-present payments from your POS system to a Kushki ONE terminal running in semi-integrated mode. The terminal handles the entire cardholder interaction and the EMV chip: your system sends the amount and receives the result.
Requirements
How it works
Every endpoint shares the same request and response structure, no matter how you connect your POS to the terminal. The only thing that changes between topologies is the base URL.
| Topology | Base URL |
|---|---|
| Local Network (LAN / Wi-Fi) | http://{TERMINAL_IP}:6868/terminal/v1 |
| Cloud (Internet) — UAT | https://uat-cloudt.kushkipagos.com/terminal/v1/{terminalSerial} |
| Cloud (Internet) — Production | https://cloudt.kushkipagos.com/terminal/v1/{terminalSerial} |
Sync and async modes
Every operation that requires terminal interaction exists in two variants, under separate path prefixes.
| Mode | Prefix | Behavior | Where the result arrives |
|---|---|---|---|
| Sync | /sync/ | Holds the connection open until the acquirer answers | In the HTTP response |
| Async | /async/ | Returns immediately with a TERMINAL_ACKNOWLEDGED acknowledgement. Never blocks | Webhook only |
Available operations
| Operation | Path | Sync | Async | What it does |
|---|---|---|---|---|
| Sale | /charge | ✅ | ✅ | Immediate charge: authorizes and captures in a single step |
| Authorization | /authorization | ✅ | ✅ | Reserves funds without capturing them |
| Capture | /capture | ✅ | ✅ | Charges the funds from an approved authorization |
| Re-authorization | /re_authorization | ✅ | ✅ | Extends the amount or the validity of an active authorization |
| Post-tip | /pos_tip | ✅ | ✅ | Adds a tip to an already-authorized transaction |
| Void | /void | ✅ | ✅ | Voids a transaction on the same day, before the cut-off |
| Refund | /refund | ✅ | — | Returns funds from a settled transaction |
| Abort | /abort | ✅ | ✅ | Cancels an operation in progress on the terminal |
| Online search | /transaction_search_online | ✅ | — | Queries transaction history at the acquirer |
| Local search | /transaction_search_local | ✅ | — | Queries the history stored on the terminal |
Key concepts
client_transaction_id
A UUID v4 identifier that your POS system generates. It works as an idempotency key: if the terminal already processed that identifier, it detects the duplicate and prevents a double charge.
Generate a new one for every sale. Reuse it only when you are retrying the exact same operation after a network failure.
transaction_reference
An identifier that Kushki generates and returns in the response of every approved transaction, inside rawResponse. It is the link between operations in the same lifecycle.
Amount structure
The amount object is sent in the currency’s minor units, as an integer. The number of decimals depends on the country.
Optional sale fields
These fields apply to /charge. The terminal handles the cardholder prompts and ignores the field when the capability is not enabled in the DMS.
| Field | Type | What it does |
|---|---|---|
amount.tip | integer | Adds a tip to the total |
cashback_amount | integer | Cash withdrawal on top of the purchase. Send 0 if it does not apply |
query_deferred | boolean | With true, the terminal offers installment options to the cardholder |
On /re_authorization you also have omit_card: with true, the operation runs without asking for the card.
Payment flows
Direct sale
The most common retail flow. The terminal activates the reader when it receives the request and waits for the cardholder to complete the interaction.
POST /sync/charge → 200 OK (approved: true)
Pre-authorization and capture
Use this when the final amount is unknown at the time of the interaction: hotels, fuel stations, open-tab restaurants.
POST /sync/authorization → store transaction_reference↓ (hours or days later)POST /sync/capture → with the stored transaction_reference
You can insert /re_authorization to raise the reserved amount or extend the window before capturing. Send subtotal_iva0: 0 to extend the validity only.
Authorization validity
| Card type | Validity from authorization |
|---|---|
| Debit (Visa / Mastercard) | 7 days |
| Credit (Visa / Mastercard) | 28 days |
The capture can reach up to 110% of the total authorized, adding the authorization and every non-canceled re-authorization. Only one capture is allowed per cycle.
Post-tip
Adds a tip to an already-authorized transaction. Use it when the cardholder decides the amount after the initial charge. Send the value in amount.tip alongside the original transaction_reference.
Void and refund
| Operation | When to use it |
|---|---|
Void (/void) | The same day as the transaction, before the processor’s cut-off time |
Refund (/refund) | Once the transaction has settled, or if the cut-off has passed |
Authentication
Include these headers with every request:
| Header | Value |
|---|---|
Authorization | HMAC-SHA256 signature of the request body, Base64-encoded, using your Business-Code as the key |
timestamp | Unix timestamp in milliseconds |
Direct sale example
A 120.00 COP charge with no tax breakdown.
- Javascript
- Python
const payload = {amount: {iva: 0, subtotal_iva: 0, subtotal_iva0: 12000,extra_taxes: { airport_tax: 0, iac: 0, ice: 0, travel_agency: 0 },},client_transaction_id: crypto.randomUUID(),};const res = await fetch(`${BASE_URL}/sync/charge`, {method: "POST",headers: buildHeaders(payload),body: JSON.stringify(payload),});const data = await res.json();console.log(data.approved, data.rawResponse.transaction_reference);
import uuid, requestspayload = {"amount": {"iva": 0, "subtotal_iva": 0, "subtotal_iva0": 12000,"extra_taxes": {"airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0},},"client_transaction_id": str(uuid.uuid4()),}res = requests.post(f"{BASE_URL}/sync/charge",headers=build_headers(payload), json=payload)data = res.json()print(data["approved"], data["rawResponse"]["transaction_reference"])
Best practices
- Generate a unique
client_transaction_idper sale and reuse it only when retrying the same operation. - Persist the
transaction_referenceas soon as you receive the authorization or charge response. - Check that the
amountfields add up to the expected total before you send the request. - Log the
client_transaction_idand the HTTP code of every operation to make reconciliation and support easier. - Call
/abortonly while a transaction is active on the terminal. Calling it afterwards returns409 Conflict. - Sign exactly the same bytes you send: serialize once, sign that string and send that same string.
Async services and webhooks
Integrate async mode: lifecycle states, event structure and the delivery retry policy.
Error catalog
Look up the error models, the codes by category and the corrective actions for every endpoint.
Chile
Ecuador
Mexico
Peru