Start charges without blocking your point of sale and receive the full transaction lifecycle by webhook, one event at a time
🚧 Early Release — Beta
This product is in early access. Parameters, response structures and behaviors may change without prior notice. Do not use this version in production without coordinating with the Kushki integrations team first.
A card-present charge depends on a person: someone has to tap the card, pick installments, type a PIN. That time routinely exceeds the 15 seconds most point-of-sale architectures tolerate.Async services solve that problem. Your system sends the payment intent, receives an immediate acknowledgement and is released. Kushki ONE Connect notifies you of every state change by webhook, through the acquirer's final answer.
1. POST /async/charge → we answer TERMINAL_ACKNOWLEDGED immediately
2. POST to your events_webhook_url → TERMINAL_ACKNOWLEDGED (first event, 1 to 2 s later)
3. The terminal operates the card → CARD_PRESENTED, APPROVAL_REQUESTED
4. Last event → APPROVAL, DECLINED, TERMINAL_REJECTED or TERMINAL_CANCELED,
with the operation detail
The acknowledgement is not the result
The response to an async call only confirms that the terminal received the payment intent. It does not tell you whether the transaction was approved. To learn the outcome you must consume the webhook.
events_webhook_url is accepted only on /async/ endpoints. Sync operations return the result directly in the HTTP response, so they emit no events.If you omit the parameter on an async call, the transaction still runs, but you receive no notifications and no final outcome.
The response and the events do not share one shape#
You receive two kinds of message, and you should deserialize them separately:
The HTTP response to your async call. Always status: TERMINAL_ACKNOWLEDGED with an empty previous_status. It echoes your request in operation and carries a terminal object in camelCase.
Each webhook delivery. It starts with its own TERMINAL_ACKNOWLEDGED (with an event_id different from the response's), continues with the state changes and ends with the acquirer's result. Here terminal is in snake_case, and operation and raw_response arrive only in the final event.
In the response, operation.type takes the values charge, pre-auth, re-auth and capture. On re-authorization and capture, operation also includes the transaction_reference you sent.The errors the call can return (400, 401, 409, 422, 500) are in the API reference: Charge (Async) — Local Network · Charge (Async) — Cloud.
Once the transaction reaches the acquirer you can no longer cancel it with abort: you have to wait for the final event. If it is approved, reverse it with a void the same day or with a refund after the cutoff.
A card rejected at the terminal can be presented again and produce a new CARD_PRESENTED. That is why the lifecycle is a graph, not a straight line: do not assume a fixed number of events per transaction.DECLINED and TERMINAL_REJECTED are the two final rejection states. Treat them the same way in your consumer and read the cause in failure_reason.
The terminal's wait time is not a fixed value
The terminal rejects the operation if the cardholder does not present the card or complete the PIN within its wait time. That time depends on the terminal model and the acquirer's configuration, and Kushki does not yet publish a general value. Do not implement your own timer that competes with the terminal's: wait for the TERMINAL_REJECTED event (with failure_reason) or TERMINAL_CANCELED, and if your POS needs to give up earlier, send abort. If you lose connectivity while waiting, reconcile with the transaction search endpoint.
An approved charge with no incidents produces four events. In certification tests, the full cycle took between 8 and 33 seconds, a rejection 11 seconds and a cancellation at the terminal 2.5 seconds; the longest wait is always the cardholder's, between TERMINAL_ACKNOWLEDGED and CARD_PRESENTED.
An approved contactless charge produces the first four events. The first three are light; the final one carries the operation and, if approved, the acquirer's response. The last two tabs show the final events of a rejection and a cancellation.
Unique event identifier. Use it to discard duplicates
occurred_at
Always
UTC date and time, ISO 8601 with milliseconds
status
Always
Current state, one of the seven above
merchant_id
Always
Kushki merchant identifier
client_transaction_id
Always
The one you sent in the request. Use it to correlate
terminal
Always
The device's serial_number, brand and model
previous_status
From the 2nd event
Previous state. The first event does not include it
reading_type
From CARD_PRESENTED
CHIP, CONTACTLESS or MAGNETIC_STRIPE
operation
Final event
The operation as the terminal processed it: type (CHARGE, PREAUTH, REAUTHORIZATION, CAPTURE), amount, metadata, events_webhook_url and the operation flags. Arrives on APPROVAL, TERMINAL_REJECTED and TERMINAL_CANCELED
operation.transaction_reference
Final event, on re-authorization and capture
The original reference you sent in the request (the pre-auth's)
failure_reason
TERMINAL_REJECTED and DECLINED
type, code and message of the rejection. Interpret it with the Error catalog. TERMINAL_CANCELED does not include it
raw_response
Only on APPROVAL
The acquirer's response: transaction_reference, ticket_number, transaction_status, transaction_type, authorized_amount, kushki_response, franchise, card_type, card_data and ISO 8583 fields (F11, F38, F39, F55)
raw_response.transaction_reference
Only on APPROVAL
The new reference of this operation. Store it: it is the one you need to capture, re-authorize, void or refund
Two references in the same event
On a capture or re-authorization, operation.transaction_reference repeats the pre-auth reference you sent, and raw_response.transaction_reference carries the new reference of the capture or re-authorization. To reconcile and void, always use the one in raw_response.
Events use snake_case
Every field in the event, including those inside terminal, operation and raw_response, is in snake_case (serial_number, subtotal_iva0, events_webhook_url). The immediate HTTP response is the exception: its terminal object uses camelCase (serialNumber, wifiMac). Deserialize each message exactly as it arrives.
The event does not include the card number
The webhook carries neither the PAN nor the cardholder name. The final event does carry the card brand, the card type and, in raw_response.card_data, the issuing bank, the brand and the country of issue.
Do not rebuild requests from the event
operation.amount in the final event is not a copy of your request: it omits iva and subtotal_iva when they are zero and adds currency and tip. Do not reuse an amount from the event to build a new request. Read Amount format in Kushki ONE.
Your endpoint must acknowledge receipt with any 2xx code. Any other response is evaluated against this policy:
Outcome
Behavior
2xx
Successful delivery. No retries
Timeout, closed connection or DNS failure
Retries
408, 429, 500, 502, 503, 504
Retries
400, 401, 403, 404, 409, 422
No retry: treated as a permanent rejection
Retries use exponential backoff with random jitter, on a 2-second base:
wait = min(60s, base * 2^attempt) + random(0..base)
Delivery stops after 10 attempts or 15 minutes, whichever comes first.
Offline resilience
If the terminal loses connectivity, it queues the events persistently and resends them when the network returns, preserving per-transaction order. A burst of delayed events after an outage is normal behavior, not a fault.
Because events are retried and replayed from the queue, your endpoint must be idempotent.
1.
Discard duplicates by event_id. Retries and replays repeat the same identifier. Store the ones you have already processed.
2.
Correlate by client_transaction_id. Every event of a transaction shares it. terminal.serial_number tells you which device generated it, but it is not a correlation key.
3.
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 with the transaction search endpoint (Cloud or Local Network, depending on your integration topology).
4.
Respond fast and process later. Return 2xx immediately and queue the payload in your internal system. A slow endpoint triggers retries, and retries cost you duplicates.
5.
Treat APPROVAL, DECLINED, TERMINAL_REJECTED and TERMINAL_CANCELED as final. No more events follow. On APPROVAL, persist raw_response.transaction_reference and raw_response.ticket_number; on rejections, record failure_reason.
JavaScript
Python
Take payments with Kushki ONE
Review every available payment flow and its sync variants.
Error catalog
Interpret the failure_reason object of the TERMINAL_REJECTED and DECLINED states.