1. Kushki One
Kushki Docs
English
  • Español
  • English
  • Home
  • Quick Start
    • Frequently Asked Questions
    • Moving to Production
    • Policy for Important API Changes
    • Test Data
    • Error Codes
    • Latest Features
  • Payment Glossary
    • Payments Glossary
  • One-time card payments (Online)
    • Supported card brands
    • Functionalities by business model
    • Network Tokens
    • One-step Payments
      • How do Card Payments Work?
      • Accept a Card Payment
      • Direct charge (without token)
      • Accept One-Time Payments with OTP
      • OTP Authentication Error Codes
      • Error codes for single card payments
    • Two-steps Payments
      • How do one-time payments with authorization and capture work?
      • Receive payments with authorization and capture
    • Webpay Plus Payments
      • Accept Payments through Webpay Plus
      • Reserve funds with Webpay Plus
    • Refunds
      • Returns, cancellations, and refunds
    • 3DS Authentication
      • Introduction to 3DS
      • 3DS API integration
      • 3DS integration with kushki.js library
      • 3DS API integration plus kushki.js library
      • 3DS Error Codes
    • Apple Pay Payments
      • Apple Pay Configuration
      • Apple Pay Sandbox testing
  • Recurring Card Payments (Online)
    • Functionalities by business model
    • External subscriptions to Kushki
    • Authenticate your subscriptions with 3DS
    • Accept one-click payments with 3DS
    • Error codes in recurring charges
    • Apple Pay for Subscriptions
    • Scheduled payments
      • How do Credit or Debit Card Payment Recurring Charges Work?
      • Create a recurring charge
      • Manage your Scheduled Charges
    • OneClick Webpay Scheduled Payments
      • Recurring Payments through Webpay OneClick
      • Reserve funds in subscriptions with Webpay OneClick
    • One Click Payments with Webpay OneClick
      • Webpay OneClick Payments
      • Reserve funds in one click payments with Webpay OneClick
    • OneClick Payments
      • Accepting One-Click Payments
      • How does authorization and capture work in OneClick payments?
      • Receive OneClick payments with authorization and capture
  • Card-present payments
    • Accept card present payments
    • Test data
    • Webhooks for card present
    • Raw card-present API
      • Raw card-present API overview
      • Cryptographic key exchange
      • Sample script to encrypt information
      • Take Payments
      • Frequently Asked Questions
      • Error codes for card-present transactions
    • Transactions
      • Transactions
      • Cancel payments
    • Kushki One
      • First Steps
      • Autenticación y Cifrado de Requests
      • Accept payments with Kushki One
      • Amount Format
      • Async services and webhooks
      • Printing on SmartPOS terminal
      • Unified Error Catalog — Kushki ONE Connect
    • Kushki One Payment App
      • Overview
      • Process a payment
      • Split payment on the terminal
      • Refunds and PreAuth & Capture
      • Review Your Transactions
      • Terminal Setup
  • Wire Transfers
    • Banks and transaction details for bank transfer payments.
    • Receive Wire Transfers
    • Check the Status of a Transaction
  • Cash Payments
    • How do Cash Payments Work?
    • Accept a Cash Payment
    • Authorized locations
    • Error Codes for declined cash transaction
  • Money Disbursement
    • Wire Transfer
      • How does the dispersion by means of bank transfer work?
      • Payment Distribution to Bank Accounts
      • Error codes for transfer dispersions
  • Plugins
    • Shopify
      • Shopify
      • Plugin Operation
    • VTEX
      • VTEX Integration Guide
  • Payment Links
    • Smartlinks
  • Payment forms and buttons
    • Kajita
      • How Does it Work?
      • Payment Kajitas
    • Payment Button
      • Overview
      • Generate a payment button
  • Branch Offices Management
    • How to Onboard Submerchants via API
  • Notifications
    • Good Practices
    • Check your Webhooks
    • One-time Payments Webhooks
      • Webhook for One-Time Payments made with a card
      • Webhook for One-Time Payments by Wire Transfer
      • Webhook for One-Time Payments in Cash
      • Webhook for Payments Made with Webpay Plus
    • Recurring Payments Webhooks
      • Webhook for Recurring Payments Made with Card
      • Webhook for recurring payments with Webpay OneClick
    • Payment Distribution Webhooks
      • Webhook for payment disbursement through wire transfer
    • Payment button Webhooks
      • Webhooks with Payment Button
    • Smartlinks Webhooks
      • Webhooks with Smartlinks
    • Refunds Webhooks
      • Webhook for Voids and Refunds
      • Chargeback Webhooks
HomeMexico 🇲🇽Ecuador 🇪🇨Chile 🇨🇱
Colombia 🇨🇴Peru 🇵🇪
HomeMexico 🇲🇽Ecuador 🇪🇨Chile 🇨🇱
Colombia 🇨🇴Peru 🇵🇪
Status
Soporte / Support
English
  • Español
  • English
  1. Kushki One

Async services and webhooks

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.

Requirements#

Before you start
A public HTTPS endpoint that accepts POST and answers with any 2xx.
The async route configured in your integration: the /async/ prefix instead of /sync/.
A webhook consumer prepared for repeated events. We explain why in Build your consumer.

How it works#

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.

Enable the events#

Send events_webhook_url in the request body of any async operation.
{
  "events_webhook_url": "https://api.yourbusiness.com/webhook/terminal-events",
  "amount": {
    "iva": 0,
    "subtotal_iva": 0,
    "subtotal_iva0": 12100,
    "extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
  },
  "client_transaction_id": "82517631-2789-4efb-8018-6234f35be2b6",
  "metadata": {
    "reference": "ABC12345",
    "customer_email": "customer@example.com",
    "device": "SUNMI-T2"
  }
}
Async mode only
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.
HTTP response to a POST /async/charge:
{
  "event_id": "9cc0ee68-6454-4288-a8fc-4514abe6c453",
  "previous_status": "",
  "occurred_at": "2026-10-12T21:40:43.311Z",
  "status": "TERMINAL_ACKNOWLEDGED",
  "merchant_id": "20000000104598300000",
  "client_transaction_id": "82517631-2789-4efb-8018-6234f35be2b6",
  "terminal": {
    "serialNumber": "TJ54241P20911",
    "model": "P2SE-BPKT",
    "wifiMac": "",
    "room": "3.0.10",
    "terminal_id": "00000001"
  },
  "operation": {
    "type": "charge",
    "amount": {
      "iva": 0,
      "subtotal_iva": 0,
      "subtotal_iva0": 12100,
      "extra_taxes": { "airport_tax": 0, "iac": 0, "ice": 0, "travel_agency": 0 }
    },
    "client_transaction_id": "82517631-2789-4efb-8018-6234f35be2b6",
    "metadata": { "reference": "ABC12345", "customer_email": "customer@example.com", "device": "SUNMI-T2" },
    "events_webhook_url": "https://api.yourbusiness.com/webhook/terminal-events"
  }
}
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.

Lifecycle states#

Kushki ONE models a payment as seven states. Five come from the terminal; only APPROVAL and DECLINED come from the acquirer.
statusOriginWhat it means
TERMINAL_ACKNOWLEDGEDTerminalThe terminal received the payment intent. Always the first state
TERMINAL_CANCELEDTerminalThe cardholder canceled on the terminal, or your POS sent an abort
CARD_PRESENTEDTerminalThe cardholder operated the card. reading_type tells you how it was read
TERMINAL_REJECTEDTerminalRejected locally before reaching the acquirer: wait time exhausted, maximum retries or validation. failure_reason tells you the cause
APPROVAL_REQUESTEDTerminalThe transaction was sent to the acquirer for authorization
DECLINEDAcquirerThe acquirer declined the transaction
APPROVALAcquirerThe acquirer approved the transaction

Transitions#

TERMINAL_ACKNOWLEDGED ─┬─→ CARD_PRESENTED ──┬─→ APPROVAL_REQUESTED ─┬─→ APPROVAL
                       │         │      ▲   │                       └─→ DECLINED
                       │         │      └───┘  card presented again
                       │         └─→ TERMINAL_CANCELED
                       ├─→ TERMINAL_CANCELED
                       └─→ TERMINAL_REJECTED
APPROVAL_REQUESTED is the point of no return
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.

Event structure#

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.
1 · TERMINAL_ACKNOWLEDGED
2 · CARD_PRESENTED
3 · APPROVAL_REQUESTED
4 · APPROVAL
TERMINAL_REJECTED
TERMINAL_CANCELED
{
  "event_id": "e7fc8be2-8186-418f-9515-99a8f638ecea",
  "occurred_at": "2026-10-12T21:40:44.800Z",
  "status": "TERMINAL_ACKNOWLEDGED",
  "merchant_id": "20000000104598300000",
  "client_transaction_id": "82517631-2789-4efb-8018-6234f35be2b6",
  "terminal": { "serial_number": "TJ54241P20911", "brand": "SUNMI", "model": "P2SE-BPKT" }
}
FieldPresentDescription
event_idAlwaysUnique event identifier. Use it to discard duplicates
occurred_atAlwaysUTC date and time, ISO 8601 with milliseconds
statusAlwaysCurrent state, one of the seven above
merchant_idAlwaysKushki merchant identifier
client_transaction_idAlwaysThe one you sent in the request. Use it to correlate
terminalAlwaysThe device's serial_number, brand and model
previous_statusFrom the 2nd eventPrevious state. The first event does not include it
reading_typeFrom CARD_PRESENTEDCHIP, CONTACTLESS or MAGNETIC_STRIPE
operationFinal eventThe 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_referenceFinal event, on re-authorization and captureThe original reference you sent in the request (the pre-auth's)
failure_reasonTERMINAL_REJECTED and DECLINEDtype, code and message of the rejection. Interpret it with the Error catalog. TERMINAL_CANCELED does not include it
raw_responseOnly on APPROVALThe 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_referenceOnly on APPROVALThe 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.

Delivery and retries#

Your endpoint must acknowledge receipt with any 2xx code. Any other response is evaluated against this policy:
OutcomeBehavior
2xxSuccessful delivery. No retries
Timeout, closed connection or DNS failureRetries
408, 429, 500, 502, 503, 504Retries
400, 401, 403, 404, 409, 422No 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.

Build your consumer#

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.
Modificado en 2026-10-14 15:41:42
Anterior
Amount Format
Siguiente
Printing on SmartPOS terminal
Built with