In Cloud mode, your POS sends HTTP requests to Kushki's cloud infrastructure, which forwards the commands to the target terminal via push. Use this mode when your POS runs in the cloud or on a network that cannot route traffic directly to the terminal.Kushki ONE Cloud is currently in Beta for Chile 🇨🇱. Do not deploy to production without coordinating with the Kushki integration team.
Base URLs#
| Environment | URL |
|---|
| Production | https://cloudt.kushkipagos.com |
| UAT | https://uat-cloudt.kushkipagos.com |
All endpoints follow the pattern:POST https://cloudt.kushkipagos.com/terminal/v1/{terminalSerial}/{mode}/{operation}
The terminalSerial is the serial number of the target SmartPOS terminal, obtained from the Device Management System (DMS). {mode} is sync or async — see below.
Authentication#
All Cloud requests require two signed headers:| Header | Description |
|---|
Authorization | HMAC-SHA256 signature of the raw JSON request body, Base64-encoded. Key = your Business-Code |
timestamp | Current Unix timestamp in milliseconds (13 digits) — must be within ±5 minutes of server time |
ℹ️ Omit Authorization on requests with no body (e.g. Abort).
Set your HTTP client timeout to at least 90 seconds. The cloud relay adds latency on top of the terminal's own processing time, which depends on how fast the cardholder interacts with the device.
Sync vs Async#
Every terminal-mediated payment operation ships in two variants, under two path prefixes.| Variant | Prefix | HTTP response | Where the outcome arrives |
|---|
| Sync | /sync/ | Blocks until the acquirer answers, then returns the full transaction result | In the HTTP response |
| Async | /async/ | Returns immediately with a TERMINAL_ACKNOWLEDGED event | Webhook only |
Async exists because card-present flows depend on human interaction and routinely exceed the ~15 second timeout budget of most POS architectures.The async response is an acknowledgement, not a result. It confirms only that the terminal accepted the payment intent. To learn whether the transaction was approved you must consume the webhook.
Supply events_webhook_url in the request body to receive events. Without it the transaction still executes, but you get no state notifications and no final outcome. The field is accepted on /async/ endpoints only.Async is available for charge, authorization, capture, re-authorization, post-tip and void. Refund, abort and transaction search are sync-only.
Every amount field is an integer — no thousands separator, no decimal separator, no spaces. This applies to subtotal_iva0, subtotal_iva, iva, tip, cashback_amount and every member of extra_taxes.The currency is always CLP, which has zero decimal places. Send the value as-is, never padded:| To charge | Send |
|---|
| 1.244 CLP | 1244 |
| 12.000 CLP | 12000 |
| 1.000.000 CLP | 1000000 |
The payload carries no currency field — the currency comes from the terminal's DMS configuration. Requests take integers, but event and webhook payloads echo amounts back as decimals (12000.0). Never re-send an echoed value as an amount.
Payment operations#
| Operation | Sync | Async | Description |
|---|
| Charge | POST /sync/charge | POST /async/charge | One-step authorization + capture |
| Authorization | POST /sync/authorization | POST /async/authorization | Reserve funds, capture later (Visa / Mastercard) |
| Capture | POST /sync/capture | POST /async/capture | Collect reserved funds |
| Re-authorization | POST /sync/re_authorization | POST /async/re_authorization | Extend or increase a pre-auth |
| Post-tip | POST /sync/pos_tip | POST /async/pos_tip | Add tip to an authorized transaction |
| Void | POST /sync/void | POST /async/void | Cancel same-day transaction (cutoff: 23:59 local) |
| Refund | POST /sync/refund | — | Refund a settled transaction |
| Abort | POST /sync/abort | — | Cancel a transaction in progress |
| Transaction Search | POST /sync/transaction_search | — | Query terminal transaction history |
Charge#
Single-step payment — authorization and capture in one operation.{
"amount": {
"subtotal_iva0": 10000,
"subtotal_iva": 0,
"iva": 0,
"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"
}
}
⚠️ Save transaction_reference from the response — required for void or refund.
Charge (Async)#
The async variant takes the same body plus the webhook URL and three terminal behaviors that the DMS gates:{
"events_webhook_url": "https://api.negocio.cl/webhook/terminal-events",
"amount": {
"subtotal_iva0": 10000,
"subtotal_iva": 0,
"iva": 0,
"tip": 0
},
"client_transaction_id": "c5a3f3be-9d6f-4d39-8af5-58dbb589af79",
"cashback_amount": 0,
"query_deferred": false
}
| Field | Purpose |
|---|
amount.tip | Tip amount. Send 0 when not applicable |
cashback_amount | Cash withdrawal amount. Send 0 for none |
query_deferred | When true, the terminal prompts the cardholder for installments (cuotas) |
When the matching capability is disabled in the DMS, the terminal ignores the field rather than rejecting the request. On /async/re_authorization, omit_card: true skips card presentation.Authorization (Pre-auth)#
Reserves funds without capturing. Use for hotels, gas stations, or open-tab scenarios.{
"amount": {
"subtotal_iva0": 10000,
"subtotal_iva": 0,
"iva": 0
},
"client_transaction_id": "auth-20240317-001"
}
| Card type | Validity |
|---|
| Debit | 7 days |
| Credit | 28 days |
Capture#
Collects funds reserved by a prior authorization. Amount must be ≤ 110% of the authorization plus all non-canceled re-authorizations. Only one capture per authorization cycle.{
"transaction_reference": "6f16659e-b711-4995-a9ae-161aecbd6521",
"amount": {
"subtotal_iva0": 10000,
"subtotal_iva": 0,
"iva": 0
},
"client_transaction_id": "cap-20240317-001"
}
Void#
Cancels a same-day transaction before the processor cutoff — approximately 23:59 local time in Chile. Wait at least 1 minute after the original transaction. After the cutoff the transaction has settled and must be reversed with a refund.{
"transaction_reference": "6f16659e-b711-4995-a9ae-161aecbd6521",
"amount": {
"subtotal_iva0": 10000,
"subtotal_iva": 0,
"iva": 0
},
"client_transaction_id": "void-20240317-001"
}
Refund#
Refunds a captured and settled transaction — can be executed days after the charge. Partial refunds are supported.{
"transaction_reference": "6f16659e-b711-4995-a9ae-161aecbd6521",
"amount": {
"subtotal_iva0": 10000,
"subtotal_iva": 0,
"iva": 0
},
"client_transaction_id": "ref-20240317-001"
}
Transaction Search#
Only page and size are required — filters and every property inside it are optional.{
"page": 1,
"size": 10,
"filters": {
"last_four_digits": "9130",
"transaction_type": "charge",
"start_date": 1785556800000,
"end_date": 1788235199000
}
}
start_date and end_date are Unix timestamps in milliseconds (13 digits). Send 0 in both to disable date filtering.A 10-digit value in seconds lands in January 1970. If only start_date is wrong, the request succeeds and returns your entire history instead of the range you asked for — check the digit count before you reconcile.
Chile observes daylight saving time (UTC-4 in winter, UTC-3 in summer), so resolve America/Santiago with a timezone library instead of hardcoding the offset:transaction_type accepts charge, preAuth, capture, reAuthorization, posTip, void and refund — camelCase, even though the endpoint paths use snake_case.
Transaction lifecycle#
A terminal-mediated payment moves through seven states. Five originate in the terminal; only APPROVAL and DECLINED come from the acquirer.| Status | Origin | Meaning |
|---|
TERMINAL_ACKNOWLEDGED | Terminal | Payment intent received. Always the first state |
TERMINAL_CANCELED | Terminal | Canceled by the cardholder, or aborted by the POS |
CARD_PRESENTED | Terminal | The cardholder presented the card. See reading_type |
TERMINAL_REJECTED | Terminal | Rejected locally — timeout, max retries, or validation |
APPROVAL_REQUESTED | Terminal | Sent to the acquirer for authorization |
DECLINED | Acquirer | The acquirer declined |
APPROVAL | Acquirer | The acquirer approved |
TERMINAL_ACKNOWLEDGED ─┬─→ CARD_PRESENTED ──┬─→ APPROVAL_REQUESTED ─┬─→ APPROVAL
│ │ ▲ │ └─→ DECLINED
│ │ └───┘ card re-presented after rejection
│ └─→ TERMINAL_CANCELED
├─→ TERMINAL_CANCELED
└─→ TERMINAL_REJECTED
APPROVAL_REQUESTED is the point of no return. Once the transaction reaches the acquirer you can no longer abort it — wait for APPROVAL or DECLINED, then reverse it with void (same day) or refund.
Event payload#
Every event — including the immediate async acknowledgement — uses the same envelope:{
"event_id": "c08211a1-344c-4f1c-850b-41e33fb08cca",
"previous_status": "TERMINAL_ACKNOWLEDGED",
"occurred_at": "2026-08-03T20:53:34.859Z",
"status": "CARD_PRESENTED",
"merchant_id": "20000000109815430000",
"client_transaction_id": "c5a3f3be-9d6f-4d39-8af5-58dbb589af79",
"interaction_attempt": 1,
"reading_type": "CHIP",
"terminal": {
"serialNumber": "TJ54241P20911",
"model": "P2SE-BPKT",
"wifiMac": "",
"room": "3.0.10"
},
"operation": {
"type": "charge",
"amount": {
"iva": 0.0,
"subtotalIva": 0.0,
"subtotalIva0": 12000.0,
"extraTaxes": { "airportTax": 0.0, "iac": 0.0, "ice": 0.0, "travelAgency": 0.0 }
},
"clientTransactionId": "c5a3f3be-9d6f-4d39-8af5-58dbb589af79",
"eventsWebHook": "https://api.negocio.cl/webhook/terminal-events",
"metadata": {
"customerEmail": "user@example.com",
"device": "SUNMI-P3",
"reference": "ORD-20240317-001"
}
}
}
Mixed casing is intentional. Top-level event fields use snake_case; everything inside terminal and operation uses camelCase (serialNumber, subtotalIva0, eventsWebHook). This mirrors the terminal's internal representation — deserialize it as-is.
| Field | Present when |
|---|
previous_status | Always. Empty string ("") on the first event |
interaction_attempt | From CARD_PRESENTED onward. Only TERMINAL_ACKNOWLEDGED can carry 0 |
reading_type | From CARD_PRESENTED onward — CHIP, CONTACTLESS or MAGNETIC_STRIPE |
failure_reason | Only on TERMINAL_REJECTED and DECLINED |
operation.transactionReference | On capture, re-authorization, post-tip and void |
Correlate all events of a transaction by client_transaction_id. Deduplicate deliveries by event_id.Webhook delivery#
Your endpoint must acknowledge with any 2xx.| Outcome | Behavior |
|---|
2xx | Success — delivery complete |
| Timeout, connection reset, DNS failure | Retry |
408, 429, 500, 502, 503, 504 | Retry |
400, 401, 403, 404, 409, 422 | No retry — permanent rejection |
Backoff is exponential with jitter (base = 2s): delay = min(60s, base * 2^attempt) + random(0..base). Delivery stops after 10 attempts or 15 minutes, whichever comes first.If the terminal loses connectivity it queues events locally and replays them once the network returns, preserving per-transaction ordering. Make your consumer idempotent: use event_id to discard duplicates and previous_status to detect gaps.
Print operations#
| Operation | Endpoint | Description |
|---|
| Create Print Job | POST /sync/print | Queue a print job on the terminal printer |
| Get Print Job Status | POST /sync/print_job | Poll the status of a queued print job |
Printing is asynchronous — the create endpoint returns 202 Accepted immediately. Supply a webhookUrl or poll for the result. Both endpoints require the same signed Authorization and timestamp headers as the Payment API.Create Print Job#
Write type in lowercase; every other enum value (align, dividerType, algorithm, errorLevel) is UPPERCASE.{
"printJobId": "RECEIPT-20240317-001",
"webhookUrl": "https://pos.yourstore.com/webhooks/print",
"skipIfBusy": false,
"commands": [
{ "type": "text", "text": "MI COMERCIO CHILE\n", "align": "CENTER", "size": 32, "bold": true },
{ "type": "divider", "dividerType": "SOLID", "offset": 10 },
{ "type": "columns", "columns": [
{ "text": "Producto Premium", "weight": 2, "align": "LEFT" },
{ "text": "$10.000", "weight": 1, "align": "RIGHT" }
]},
{ "type": "divider", "dividerType": "DOTTED", "offset": 10 },
{ "type": "text", "text": "TOTAL: $10.000\n", "align": "RIGHT", "size": 32, "bold": true },
{ "type": "qr", "content": "https://facturacion.micomercio.cl/ticket/7788", "dotSize": 8, "errorLevel": "H", "align": "CENTER" },
{ "type": "feed", "lines": 3 },
{ "type": "cut" }
]
}
{
"printJobId": "RECEIPT-20240317-001",
"status": "PENDING",
"message": "Impresión encolada correctamente."
}
ℹ️ Reuse the same printJobId on retries — the terminal deduplicates by it and will not print twice. Always end with feed + cut, advancing at least 3 lines.
Get Print Job Status#
print_job_id travels in the request body, not as a query parameter. Poll every 2–3 seconds and stop on COMPLETED or FAILED.{
"print_job_id": "RECEIPT-20240317-001"
}
| Status | Description |
|---|
PENDING | Job queued — not yet printed |
COMPLETED | Printed and cut successfully |
FAILED | Hardware error — see errorCode |
Available Endpoints#
Charge
One-step authorization + capture.
Charge (Async)
Non-blocking charge. Outcome arrives on the events webhook.
Authorization
Reserve funds for later capture.
Authorization (Async)
Non-blocking pre-authorization.
Capture
Collect reserved funds from a prior authorization.
Capture (Async)
Non-blocking capture.
Re-authorization
Extend or increase a pre-authorization.
Re-authorization (Async)
Non-blocking re-authorization. Supports omit_card.
Post-tip
Add gratuity to an authorized transaction.
Post-tip (Async)
Non-blocking post-tip.
Void
Cancel a same-day transaction before cutoff.
Void (Async)
Non-blocking void.
Refund
Refund a settled transaction. Sync only.
Abort
Cancel a transaction currently in progress. Sync only.
Transaction Search
Query terminal transaction history. Sync only.
Create Print Job
Queue a print job. Returns immediately — result via webhook or polling.
Get Print Job Status
Poll the status of a queued print job.
Got a suggestion on this documentation? Contact us.