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
The payloads and the headers are identical between topologies. The routes are not β four things change, and they are listed under the base URLs.
| Topology | Base URL |
|---|---|
| Local Network (LAN / Wi-Fi) | http://{TERMINAL_IP}:6868/terminal/v1 or https://{TERMINAL_IP}:6869/terminal/v1 |
| Cloud (Internet) β UAT | https://uat-cloudt.kushkipagos.com/terminal/v1/{terminalSerial} |
| Cloud (Internet) β Production | https://cloudt.kushkipagos.com/terminal/v1/{terminalSerial} |
What changes between Cloud and the local network, beyond the base URL:
| Difference | Local Network | Cloud |
|---|---|---|
| Online search route | /transaction_search_online | /sync/transaction_search_online |
/abort method | GET, in both modes | POST, sync only |
| Async abort | Available | Does not exist |
| Print routes | /terminal/v1/print | /{terminalSerial}/sync/print |
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 |
| Reversal | /void | β | β | Reverses a transaction: cancellation within the calendar day, refund after the cut-off |
| 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 fields, by operation
These are the only optional fields, and each one belongs to specific operations. They behave the same in sync and async.
| Field | Type | Applies to | What it does |
|---|---|---|---|
amount.tip | integer | /charge and /authorization | Adds a tip. The tip can be set at pre-authorization time, not only when charging |
cashback_amount | integer | /charge | Cash withdrawal on top of the purchase. Send 0 if it does not apply |
deferred / query_deferred | object / boolean | /charge | Installments. The field and its shape change by country β see below |
omit_card | boolean | /capture, /re_authorization and /void | With true the operation runs without asking for the card |
metadata | object | /charge, /authorization and /pos_tip | Free-form traceability: reference, customer_email, device |
On /pos_tip, amount.tip is not an optional field β it is the amount of the operation.
omit_card is what makes the hotel case work: extending or capturing a pre-authorization after the guest has left the desk.
Installments change by country
The two fields are mutually exclusive and never travel together:
| Country | Field | Shape | Maximum |
|---|---|---|---|
| Mexico | query_deferred | Boolean. The terminal offers Meses Sin Intereses to the cardholder after reading the card; your POS does not choose the months | β |
| Chile | deferred | months, plus credit_type: "03" for cuotas comercio β a string, not a number | 12 with credit_type, 48 without it |
| Colombia | deferred | months only. credit_type does not apply | 48 |
| Peru | deferred | months only. credit_type does not apply | 2β48, every network |
If a capability is not enabled for your terminal, the terminal ignores the field instead of rejecting the request β so a 200 does not prove the tip was applied. To have it enabled, write to soporte@kushkipagos.com.
Reading the HTTP status
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.
Reversal
There is a single reversal endpoint, /void, and the system decides what the operation becomes based on when you call it. The cut-off is 23:59 local time in every country: within the same calendar day the reversal is a cancellation; from midnight onwards it enters the refund cycle and takes business days.
Wait at least 1 minute after the original transaction before reversing it, or it fails for no apparent reason.
The type returned by transaction search tells you what actually happened:
| Type | What it means |
|---|---|
VOID | You called /void on the same calendar day. Cancellation β the cardholder never sees the charge |
REFUND | You called /void after the cut-off. It entered the refund cycle and takes business days |
REVERSE | You did not ask for it. The platform generates it on its own when communication with the terminal fails |
Authentication
Kushki ONE uses one mechanism β hash + encryption β and it is the same in Cloud, on the local network and on localhost: Authorization: Basic <SHA512>, timestamp in seconds, and the body as the encrypted envelope {"data":"<iv_hex>:<cipher_hex>"}.
Every payload shown on this page is the plaintext you encrypt, not what travels on the wire. The full flow, the key derivation chain and the diagnostic tree are on Authentication and request encryption.
Direct sale example
A charge of 12000 minor units with no tax breakdown. What that represents depends on the countryβs decimals β check Amount format in Kushki ONE before your first integration.
- 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