Kushki ONE is currently in Beta for Colombia 🇨🇴. Endpoints, parameters and response structures may change without prior notice. Do not deploy to production without coordinating with the Kushki integration team.
Kushki ONE pushes real-time notifications from the terminal to your backend over two independent webhook channels. They do not share a payload structure, an enabling parameter, or a delivery policy — read the section for the channel you are integrating.
Both channels use POST with Content-Type: application/json, and both require HTTPS in production.Delivers a notification on every state change of a terminal-mediated payment.
Webhooks are emitted by async operations only. Sync operations block until the acquirer answers and return the result directly in the HTTP response, so they have nothing to notify. Omitting events_webhook_url on an async call is valid — the transaction still runs, but you receive no state notifications and no final outcome.
APPROVAL_REQUESTED is the point of no return. Once the transaction reaches the acquirer it can no longer be aborted — you must wait for APPROVAL or DECLINED. If it is approved, reverse it with void (same day, before roughly 23:59 local time in Colombia) or refund.
A card rejected at the terminal can be presented again, producing another CARD_PRESENTED with an incremented interaction_attempt. This is why the lifecycle is a graph, not a straight line: do not assume a fixed number of events per transaction.
State before this event. Empty string ("") on the first event.
occurred_at
string
✅
ISO-8601 UTC with milliseconds.
status
string
✅
Current lifecycle state. One of the seven above.
merchant_id
string
✅
Kushki merchant identifier.
client_transaction_id
string (UUID)
✅
From your request. Use this to correlate events.
interaction_attempt
integer
⬦
Card-interaction counter. From CARD_PRESENTED onward.
reading_type
string
⬦
CHIP | CONTACTLESS | MAGNETIC_STRIPE. From CARD_PRESENTED onward; refreshed on each new read.
failure_reason
object
⬦
{ type, code, message }. Only on TERMINAL_REJECTED and DECLINED.
terminal
object
✅
{ serialNumber, model, wifiMac, room }.
operation
object
✅
Snapshot of the originating operation.
operation.transactionReference
string (UUID)
⬦
On capture, re_authorization, pos_tip and void.
✅ always present · ⬦ conditionalThe Payment API reference is the authoritative contract for this payload — see the TransactionEvent schema on each async operation under Cloud Services or Local Network Services. The table above is a reading aid.
Mixed casing is intentional
Top-level 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; do not normalize.
Amounts are echoed as decimals
Requests take integers in the smallest unit of COP, but events echo them back as decimals (12000.0). Never reuse an amount taken from an event to build a new request — see Building the amount.
INFO
No cardholder data is delivered. The transaction webhook carries no PAN, no cardholder name, and no card network. If you need card details, read them from the sync response or from Transaction Search.
Acknowledge with any 2xx. Anything else is evaluated against this policy:
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 — treated as 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.
INFO
Offline resilience. If the terminal loses connectivity it holds events in a persistent local queue and replays them once the network returns, preserving per-transaction ordering. A burst of delayed events after an outage is normal behavior, not a fault.
Because events are retried and replayed, your endpoint must be idempotent.
Deduplicate by event_id. Retries and queue replays repeat the same event_id. Persist the ones you have processed and discard repeats.
Correlate by client_transaction_id. All events of one transaction share it. terminal.serialNumber tells you which device, but is not a correlation key on its own.
Detect gaps with previous_status. If it does not match the last state you recorded for that transaction, an event is missing or arrived out of order. Reconcile via Transaction Search.
Respond fast, process later. Return 2xx immediately and hand the payload to an internal queue. Slow endpoints trigger retries, which cost you duplicates.
Treat APPROVAL / DECLINED as final. No further events follow. For TERMINAL_REJECTED, read failure_reason.code against the Error Catalog.
{"printJobId":"RECEIPT-20240317-001","status":"FAILED","externalReference":"POS-ORDER-7788","errorCode":"COVER_OPEN","errorMessage":"The thermal printer cover was opened abruptly."}
Fire-and-forget with a 15-second timeout. If your endpoint does not answer in time or returns a non-2xx status, the terminal does not retry — it continues its flow rather than blocking the printer.
INFO
Only terminal states fire the webhook. Intermediate PENDING and IN_PROGRESS transitions are never delivered. Poll Get Print Job Status if you need them.
Because there are no retries on this channel, build a polling fallback for flows where a lost print confirmation matters.