Kushki ONE is currently in Beta for Chile 🇨🇱. Endpoints, parameters and response structures may change without prior notice. Do not deploy to production without coordinating with the Kushki integration team.
Practical, copy-ready request examples for every payment operation, in both topologies. If you are integrating for the first time, read Building the amount before anything else — it is where most integrations go wrong.
Base URL#
| Topology | Base URL |
|---|
| Local Network | http://{terminalIp}:{port}/terminal/v1 |
| Cloud — Production | https://cloudt.kushkipagos.com/terminal/v1/{terminalSerial} |
| Cloud — UAT | https://uat-cloudt.kushkipagos.com/terminal/v1/{terminalSerial} |
Every path below is shown relative to that base. The only structural difference between topologies is the {terminalSerial} segment in Cloud:POST /terminal/v1/sync/charge ← Local
POST /terminal/v1/{terminalSerial}/sync/charge ← Cloud
In Local Network mode the terminal exposes an HTTP server on its own IP. The defaults are 192.168.1.50 for terminalIp and 6868 for port, both given to you by the integration team during onboarding.| Header | Value |
|---|
Content-Type | application/json |
Authorization | Basic followed by the SHA-512 hash — the Basic prefix is required |
timestamp | Unix timestamp in seconds (10 digits), UTC, within ±5 minutes of server time |
The body is never sent in the clear. What travels is the encrypted envelope:{ "data": "<iv_hex>:<ciphertext_hex>" }
On GET that same value goes as the data query parameter, and no other parameter may be
sent — the identifier travels inside the ciphertext.The signature and the encryption use two different derivations of the same temporary password,
and both depend on the timestamp. Generate it once per request:You sign one object and encrypt another. The signature covers request_data plus the key
field; the ciphertext covers request_data alone. Building them from two different
serializations — or from two different timestamps — returns AUTH-001 with no further hint.
The signing key is the Business-Code, not the private_credential_id. The latter is a terminal configuration field and is never used to sign requests — signing with it returns AUTH-001 on every call.
Idempotency#
Every request carries a client_transaction_id (UUID v4). On network failure, retry with the same UUID — the terminal deduplicates and will not charge twice.
Building the amount#
All amount fields are integers in the smallest unit of CLP. No separators, no decimal point.CLP has no decimals#
The currency in Chile is CLP (Chilean Peso), which has zero decimal places. Send the value as-is — never pad it:| To charge | Send |
|---|
| 12.000 CLP | 12000 |
| 100.000 CLP | 100000 |
| 20.000 CLP | 20000 |
| 25.800 CLP | 25800 |
Padding a CLP amount with 00 charges 100× too much. Zero-decimal currencies are never padded. If your POS also serves a two-decimal market, keep the conversion per-terminal — do not share one code path.
The payload carries no currency field. The currency is configured on the terminal by the integration team, not sent in the request. If your POS serves more than one market, read currency_code from the terminal configuration and resolve the decimal handling per terminal — the same integer means different money in different markets.
Converting safely#
Never use floating-point arithmetic. In most languages 12.44 * 100 == 1243.9999999999998, which truncates to 1243 — you undercharge by one cent and your reconciliation breaks. Use integers or a decimal type.
Pass the value as a string, not a float — Decimal(12.44) inherits the binary rounding error you were trying to avoid.
Use a plain decimal string: . as the decimal point and no thousands separator. 100.000 CLP is written 100000 here. Strip your UI's separators before calling — Decimal("100.000 CLP") raises.
Amount field roles#
| Field | Meaning |
|---|
subtotal_iva | Portion of the sale subject to IVA |
iva | IVA amount on that portion. The general rate in Chile is 19% |
subtotal_iva0 | Portion exempt from IVA. Use this alone when the sale has no tax breakdown |
extra_taxes.* | Industry-specific taxes: airport_tax, iac, ice, travel_agency. Send 0 when not applicable |
tip | Tip. Travels on /charge and /authorization; on /pos_tip it is the amount of the operation |
The amount charged is the sum of all of them.
Charge#
Single-step payment: authorization and capture in one operation. The standard flow for retail.Sync — blocks until the acquirer answers#
{
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 12000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "c5a3f3be-9d6f-4d39-8af5-58dbb589af79",
"metadata": {
"reference": "ORD-20240317-001",
"customer_email": "user@example.com",
"device": "SUNMI-P3"
}
}
Returns the full result. Save rawResponse.transaction_reference — you need it for void, capture, re_authorization and pos_tip.{
"approved": true,
"responseCode": "00",
"authCode": "123456",
"rawResponse": {
"transaction_reference": "983a7480-6d97-41b2-9c3e-7f1a2b3c4d5e",
"authorized_amount": 12000,
"franchise": "VISA"
}
}
Use this when your architecture cannot hold a connection open for the duration of a card-present flow.{
"events_webhook_url": "https://api.negocio.cl/webhook/terminal-events",
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 12000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "c5a3f3be-9d6f-4d39-8af5-58dbb589af79"
}
The response is an acknowledgement, not a result:{
"event_id": "c08211a1-344c-4f1c-850b-41e33fb08cca",
"previous_status": "",
"occurred_at": "2026-08-03T20:53:34.859Z",
"status": "TERMINAL_ACKNOWLEDGED",
"client_transaction_id": "c5a3f3be-9d6f-4d39-8af5-58dbb589af79"
}
The outcome arrives at your events_webhook_url. See Webhooks for the event sequence and retry policy.Omitting events_webhook_url on an async call is valid, but then you have no way to learn the result — the transaction runs blind. Only do this if you plan to reconcile via Transaction Search.
Charge with tip, cashback or installments#
These are optional fields on charge. The terminal handles the cardholder prompts, and it ignores any field whose capability is not enabled for the terminal instead of rejecting the request.{
"events_webhook_url": "https://api.negocio.cl/webhook/terminal-events",
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 12000,
"tip": 2000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"cashback_amount": 0,
"deferred": { "months": 12 },
"client_transaction_id": "c5a3f3be-9d6f-4d39-8af5-58dbb589af79"
}
| Field | Effect |
|---|
amount.tip | Adds a tip to the total. 2000 = 2.000 CLP |
cashback_amount | Cash withdrawal on top of the purchase. 0 for none |
deferred.months | Number of installments: up to 12 with credit_type, up to 48 without it |
amount.tip travels on /charge and /authorization, and on /pos_tip it is the amount of the operation. cashback_amount travels on /charge. If the capability is not enabled for the terminal you get a CONFIGURATION error: CONF-4001 for tip, CONF-4002 for cashback. See the Error Catalog.
Pre-authorization flow#
Use this when the final amount is unknown at card-present time — hotels, fuel, open tabs.Step 1 — Authorize#
Reserves funds without capturing.{
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 50000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "a1b2c3d4-0000-4000-8000-000000000001"
}
Save rawResponse.transaction_reference. Authorization validity:| Card type | Validity |
|---|
| Debit (Visa / Mastercard) | 7 days |
| Credit (Visa / Mastercard) | 28 days |
Step 2 (optional) — Re-authorize#
Extends the amount or the capture deadline. Send 0 to extend the date only. Set omit_card: true to skip card presentation.POST /async/re_authorization
{
"events_webhook_url": "https://api.negocio.cl/webhook/terminal-events",
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 15000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "a1b2c3d4-0000-4000-8000-000000000002",
"transaction_reference": "983a7480-6d97-41b2-9c3e-7f1a2b3c4d5e",
"omit_card": false
}
A re-authorization can be canceled — but once canceled, no further re-authorizations are accepted on that transaction.
Step 3 — Capture#
{
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 65000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "a1b2c3d4-0000-4000-8000-000000000003",
"transaction_reference": "983a7480-6d97-41b2-9c3e-7f1a2b3c4d5e"
}
Maximum capture is 110% of the authorization plus all non-canceled re-authorizations.
One capture only per authorization cycle.
Post-tip#
Adds a tip to an already-approved transaction — the classic restaurant flow where the tip is decided after the card is charged.{
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 12000,
"tip": 2000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "b2c3d4e5-0000-4000-8000-000000000001",
"transaction_reference": "983a7480-6d97-41b2-9c3e-7f1a2b3c4d5e"
}
Void#
Reverses an approved transaction within the same calendar day, before the 23:59 cutoff.{
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 12000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "c3d4e5f6-0000-4000-8000-000000000001",
"transaction_reference": "983a7480-6d97-41b2-9c3e-7f1a2b3c4d5e"
}
Cutoff: 23:59 local time (Santiago). Within the same calendar day as the original transaction, /void is a cancellation and the cardholder never sees the charge. From midnight onwards the same call enters the refund cycle and takes business days. Wait at least 1 minute after the original transaction before calling void.
Abort#
Cancels an in-flight operation while the terminal is still waiting for the cardholder.| Topology | Request |
|---|
| Local Network | GET /sync/abort — also available as GET /async/abort |
| Cloud | POST /{terminalSerial}/sync/abort — sync only |
No request body in either topology. The signature is computed over an empty string.Abort only works before the transaction reaches the acquirer. Once the state machine hits APPROVAL_REQUESTED the operation can no longer be aborted and the call returns 409 — wait for APPROVAL or DECLINED, then reverse it with /void.
Complete worked examples#
Restaurant in Chile — tax breakdown and tip#
Bill: food 20.000 CLP + IVA 19% (3.800 CLP) + tip 2.000 CLP = 25.800 CLP| Component | Value | Minor units |
|---|
subtotal_iva | 20.000 CLP | 20000 |
iva | 3.800 CLP | 3800 |
tip | 2.000 CLP | 2000 |
| Total charged | 25.800 CLP | 25800 |
{
"events_webhook_url": "https://api.negocio.cl/webhook/terminal-events",
"amount": {
"iva": 3800,
"subtotal_iva": 20000,
"subtotal_iva0": 0,
"tip": 2000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "7f8e9d0c-1111-4000-8000-aabbccddeeff",
"metadata": { "reference": "MESA-14-T0042", "device": "SUNMI-P3" }
}
Retail in Chile — no tip#
Sale: 100.000 CLP plus IVA 19% (19.000 CLP) = 119.000 CLP| Component | Value | Minor units |
|---|
subtotal_iva | 100.000 CLP | 100000 |
iva | 19.000 CLP | 19000 |
| Total charged | 119.000 CLP | 119000 |
{
"amount": {
"iva": 19000,
"subtotal_iva": 100000,
"subtotal_iva0": 0,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "9a8b7c6d-2222-4000-8000-ffeeddccbbaa",
"metadata": { "reference": "BOL-000198472", "device": "SUNMI-P2SE" }
}
Simple sale with no tax breakdown#
When you do not itemize taxes, put the whole amount in subtotal_iva0:{
"amount": {
"iva": 0,
"subtotal_iva": 0,
"subtotal_iva0": 119000,
"extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
},
"client_transaction_id": "1a2b3c4d-3333-4000-8000-112233445566"
}
That charges 119.000 CLP — the same total as the retail example above, without the breakdown.
Common mistakes#
| Mistake | Symptom | Fix |
|---|
Padding the amount with 00 | Charging 100× too much | CLP has no decimals — 12.000 CLP is 12000, not 1200000 |
| Copying a conversion helper from a two-decimal market | Charging 100× too much | Keep the decimal exponent per terminal, not per codebase |
| Sending the amount as a string with separators | Validation error | Strip all separators; send an integer, not a string |
Using float for the conversion | Off-by-one-unit on some amounts | Use integer or decimal arithmetic |
| Reusing an amount echoed from a webhook | Wrong magnitude | Echoes are decimals (12000.0); requests are integers |
Reusing a client_transaction_id across different sales | Second sale silently deduplicated | One fresh UUID v4 per sale; reuse only when retrying the same one |
Regenerating the timestamp mid-flow | AUTH-001 | Generate it once and reuse it for the password, the signature and the header |
Signing with private_credential_id | AUTH-001 on every call | The signing key is the Business-Code |
| Treating the async response as the result | Sale marked approved when it was declined | The ack only means TERMINAL_ACKNOWLEDGED; wait for the webhook |
| Expecting card data in the webhook | Null fields in your records | The webhook carries no PAN or cardholder name — read it from the sync response or Transaction Search |
Calling /void after the 23:59 cutoff | Accepted — it enters the refund cycle | Expect REFUND in transaction search, and business days instead of minutes |
Webhooks
Event sequence, retry policy and how to build an idempotent consumer.
Error Catalog
Response codes and failure reasons, with the recommended action for each.
Got a suggestion on this documentation? Contact us.