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 Perú 🇵🇪. Do not deploy to production without coordinating with the Kushki integration team.
Base URLs#
| Environment | URL | Selector |
|---|
| Production | https://cloudt.kushkipagos.com | Kushki ONE Cloud — Producción |
| UAT | https://uat-cloudt.kushkipagos.com | Kushki ONE Cloud — UAT |
Pick one of those two in the environment selector at the top right before using Try it. The default UAT Testing Env points at api-uat.kushkipagos.com, which is the Online Payments host — Kushki ONE does not answer there.
If your POS runs behind a corporate firewall, these are the hosts to allow on port 443. Open only the row for your environment — uat-cloudt is a test host and does not belong on a production network:| Purpose | UAT | Production | Who needs it |
|---|
| Cloud relay | uat-cloudt.kushkipagos.com | cloudt.kushkipagos.com | The terminal and the POS network |
| Processing | api-uat.kushkipagos.com | api.kushkipagos.com | The terminal only |
| Terminal management | uat-tms.kushkipagos.com | tms.kushkipagos.com | The terminal only |
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 — the integration team gives it to you during onboarding, along with your Business-Code. {mode} is sync or async — see below.
Authentication#
Kushki ONE uses one authentication mechanism, and it is the same in Cloud, local network and localhost: hash + encryption. Your terminal ships with encryption enabled, so this is the only path.{ "data": "<iv_hex>:<cipher_hex>" }
| Element | Rule |
|---|
Authorization | Basic prefix followed by the SHA512 hash. The Basic prefix is required |
timestamp | Unix timestamp in seconds (10 digits) — must be within ±5 minutes of server time |
| Body | Always the encrypted envelope {"data":"<iv_hex>:<cipher_hex>"}. The payloads documented below are the plaintext you encrypt, not what travels on the wire |
On operations with no payload — /sync/abort — you sign the literal {}, not an empty string. Signing an empty string returns AUTH-001 with no further hint.
Full details, including the data query-parameter variant used by GET operations on the local network, are on the Authentication page.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. Abort and transaction search are sync-only — and in Cloud there is no /async/abort at all.
Reading the HTTP status#
On payment endpoints the HTTP status is always 200, whatever happened. It confirms that the terminal processed your request — not that the operation succeeded. Always evaluate the response body.
A POS that branches on response.ok treats a busy terminal, an expired PIN and a cardholder cancellation as successes. The print endpoints are the exception: they return real HTTP codes (404 when the job does not exist, 409 while the queue is busy).Error bodies are flat — {type, code, param, message, object}, with no wrapper. See the Error Catalog.
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 terminal is provisioned in PEN or USD — both have two decimal places, so the last two digits are always the fractional part and amounts without a fraction keep their trailing zeros:| To charge | Send |
|---|
| S/ 12.44 | 1244 |
| S/ 12.00 | 1200 |
| S/ 1,000.00 | 100000 |
The payload carries no currency field — the terminal resolves the currency from its own configuration, and the integration team confirms which one your terminal uses during onboarding. 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 |
| Reversal | POST /sync/void | POST /async/void | Reverse a transaction — see below |
| Abort | POST /sync/abort | — | Cancel a transaction in progress |
| Transaction Search | POST /sync/transaction_search_online | — | Query terminal transaction history |
| Connection test | POST /sync/local/test | — | Check that your POS reaches the terminal. Moves no money |
| Terminal info | POST /sync/config/terminal_info | — | Returns {model, room, serialNumber}. room is the app version running on the terminal |
Optional fields, by operation#
These are the only optional fields, and they are the same in sync and async:| Field | Applies to |
|---|
amount.tip | /charge and /authorization — the tip can be set at pre-authorization time, not only when charging |
cashback_amount | /charge |
deferred | /charge — installments, see below |
omit_card | /capture, /re_authorization and /void |
metadata | /charge, /authorization and /pos_tip. Subfields: reference, customer_email, device |
On /pos_tip, amount.tip is not an optional field — it is the amount of the operation.If a capability is not enabled for your terminal, the terminal ignores the field instead of rejecting the request. To have it enabled, write to soporte@kushkipagos.com.Beta coverage. Only /charge has been exercised end-to-end with these fields, in sync and async. amount.tip on /authorization and omit_card on its six combinations are defined but not yet exercised — expect to be the first to run them.
Installments (deferred)#
In Peru, installments travel in the deferred object:{ "deferred": { "months": 12 } }
Your POS sends the number of months: 2 to 48, the same range for every card network.query_deferred is Mexico only and must not be sent from Peru. The two fields are mutually exclusive and never travel together.
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",
"device": "SUNMI-P3"
}
}
⚠️ Save transaction_reference from the response — required to reverse the transaction.
Charge (Async)#
The async variant takes the same body plus the webhook URL:{
"events_webhook_url": "https://api.negocio.pe/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,
"deferred": { "months": 12 }
}
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,
"tip": 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",
"omit_card": true
}
omit_card decides whether the terminal asks the cardholder for the card. With true the operation runs without it — which is what makes the hotel case work: extending or capturing a pre-authorization after the guest has left the desk. It applies to /capture, /re_authorization and /void, in sync and async.Reversal (/void)#
There is one reversal endpoint, and the system decides what the operation becomes based on when you call it. The cutoff is 23:59 local time: within the same calendar day as the original transaction 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.{
"transaction_reference": "6f16659e-b711-4995-a9ae-161aecbd6521",
"amount": {
"subtotal_iva0": 10000,
"subtotal_iva": 0,
"iva": 0
},
"client_transaction_id": "void-20240317-001"
}
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 cutoff. It entered the refund cycle and takes business days |
REVERSE | You did not ask for it. The system generates it on its own when communication fails |
REVERSE is the one worth handling explicitly: your POS can find a reversal in transaction search that it never requested. It is not an error — it is how the platform resolves a charge whose communication was cut mid-flight.
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": "SALE",
"start_date": 1785560400000,
"end_date": 1788238799000
}
}
Available filters: bin, last_four_digits, client_transaction_id, transaction_reference, start_date, end_date and transaction_type.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.
Peru does not observe daylight saving time, so America/Lima stays on UTC-5 all year — resolve it with a timezone library anyway:transaction_type#
The filter accepts eight values, in uppercase: CAPTURE, DEFERRED, PREAUTH, REAUTH, REFUND, REVERSE, SALE, VOID.They are not the names of the routes. This table is how you correlate one transaction across the route you called, the search you queried and the event you received:| Route | Search type | Event |
|---|
/charge | SALE | charge |
/charge with deferred | DEFERRED | charge |
/authorization | PREAUTH | authorization |
/capture | CAPTURE | capture |
/re_authorization | REAUTH | re_authorization |
/void same calendar day | VOID | void |
/void after cutoff | REFUND | void |
| (system-generated) | REVERSE | — |
/pos_tip | SALE | pos_tip |
Two consequences worth knowing. A /pos_tip is recorded as a sale, so there is no dedicated type for tips — to tell one from a normal charge, look at the original transaction_reference or at the tip inside the amount. And PREAUTH here is authorization as an event name: three naming conventions for the same thing, which is why this table exists.
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.
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": "20000000103802320000",
"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.pe/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 use the same hash + encryption authentication as the payment operations.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 PERÚ\n", "align": "CENTER", "size": 32, "bold": true },
{ "type": "divider", "dividerType": "SOLID", "offset": 10 },
{ "type": "columns", "columns": [
{ "text": "Producto Premium", "weight": 2, "align": "LEFT" },
{ "text": "S/ 100.00", "weight": 1, "align": "RIGHT" }
]},
{ "type": "divider", "dividerType": "DOTTED", "offset": 10 },
{ "type": "text", "text": "TOTAL: S/ 100.00\n", "align": "RIGHT", "size": 32, "bold": true },
{ "type": "qr", "content": "https://facturacion.micomercio.pe/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 |
Unlike the payment endpoints, these return real HTTP codes: 404 if the job does not exist and 409 while the printer is busy. A TER-004 here means the printer is busy and the job can be retried in a few seconds — the same code on a payment endpoint means the cardholder canceled on the terminal, which must not be retried.
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. Supports omit_card.
Capture (Async)
Non-blocking capture. Supports omit_card.
Re-authorization
Extend or increase a pre-authorization. Supports omit_card.
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.
Reversal
Reverse a transaction. Cancellation within the calendar day, refund after cutoff. Supports omit_card.
Reversal (Async)
Non-blocking reversal. Supports omit_card.
Abort
Cancel a transaction currently in progress. Sync only — sign {}.
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.