Callbacks
Callback notification (webhook)
Checkout sends an HTTP POST to your notification_url whenever a transaction state changes. The callback is the source of truth, not the customer return URL.
Configuring your notification URL
For the Checkout protocol, notification_url is not available per request. A system admin configures it once in the admin panel for your merchant. Pass your sandbox and production URLs to your account manager to set them up.
warningA callback with
status=successdoes not always mean the payment is final. You must check bothstatusANDtype. See Final vs intermediary callbacks.
Each order processing generates a sequence of events. The type field identifies the event. The order_status field reflects the current payment or operation status.
Sequence of status changes:
-e66732865054642e3715e153fa86ad3c.png)
Transport
| Value | |
|---|---|
| HTTP method | POST |
Default Content-Type | application/x-www-form-urlencoded; charset=utf-8 |
| Body | Flat key=value&... form. Spaces are encoded as +. |
| Expected response | 200 OK (any 2xx). Body content is ignored. |
| Retry on timeout/non-2xx | Yes; see Delivery & retries. |
Event types
A callback fires for any of these type values:
type | Meaning |
|---|---|
sale | Standard purchase outcome. In DMS mode the auth result is also reported as type=sale with order_status=pending (held but not captured). |
capture | Capture of a previously authorized payment. |
refund | Refund outcome. |
void | Void of an authorization. |
recurring | Recurring (MIT) charge outcome. |
debit | Debit operation outcome. |
credit | Credit (payout to customer) outcome. |
transfer | Transfer (P2P) outcome. |
3ds | 3DS authentication step (intermediary). |
redirect | Redirect step for APMs / HPPs (intermediary). |
init | Crypto deposit initialization (intermediary; sent before the customer transfers funds, contains the wallet address). Crypto flow only. |
chargeback | Dispute opened by the cardholder (system-generated). |
reversal | Auto-reversal of a held authorization (system-generated). |
Status values
A status of success means the event identified by type completed successfully. It does not mean the overall payment is final. To determine whether a payment is complete, inspect type, status, and order_status together.
status reports the outcome of the event identified by type:
status | Meaning |
|---|---|
success | The event completed successfully. |
fail | The event failed; transaction did not progress. |
waiting | The PSP/bank has not returned a final result yet. Poll status or wait for the next callback. |
undefined | Uncertain due to upstream issues. Do not fulfill. Log + alert + reconcile manually. |
Final vs intermediary callbacks
importantPayment completion is determined by
order_status(settled,void, and so on).type+statusdescribe only an individual transaction, not the payment.
::: Example: for a 3DS payment you receive two
successcallbacks:
type=3ds, status=success: the customer was redirected to 3DS page. Payment is not yet complete.type=sale, status=success: funds captured. Now you can fulfill. :::
warningFulfilling on the first callback will result in shipping goods that were never paid for.
type | order_status | Final? | Action |
|---|---|---|---|
sale, capture, recurring, debit, transfer, credit | settled | ✅ yes | Fulfill the order. |
sale (representing AUTH in DMS) | pending | ⚠️ partially | Funds are held but not captured. Trigger capture (auto / manual / API) or void. See DMS mode. |
| any | prepare | ❌ no | Payment is being prepared; wait for the next callback. |
refund | refund | ✅ yes | Mark refunded. |
void | void | ✅ yes | Mark voided. |
3ds, redirect, init | 3ds / redirect / pending | ❌ no | Wait for the next callback. |
chargeback | chargeback | ✅ yes | Open dispute. |
reversal | reversal | ✅ yes | Unblock retry / notify customer. |
Delivery & retries
noteEach callback is delivered exactly once. There are no automatic retries. If your endpoint is unreachable, returns a non-2xx, or times out, that callback is lost from your perspective. Build your handler accordingly.
What the platform does instead:
- URL-level circuit breaker. If your
notification_urlaccumulates 5 timeouts within 5 minutes, the URL is blocked for 15 minutes, during which all merchants associated with that URL stop receiving callbacks. The block clears automatically, or can be removed manually in admin panel → Configuration → Merchants → Edit Merchant. The timeout counter resets after a single successful (2xx) response. - No per-callback retry queue. A failed callback is not re-sent automatically.
What you should do as integrator:
- Make your endpoint highly available (≥ 2 instances, behind a load balancer, with a tight DB-write path). A single 500 from your side = a missed callback.
- Acknowledge the callback fast: return
200 OKfirst, do business work asynchronously. If your handler exceeds the platform timeout, the URL-block mechanism trips. - Reconcile via
/api/v1/payment/statusas a safety net. Schedule a poll for every payment that is older than N minutes and still in a non-terminal state in your DB. - Keep your handler idempotent: the same callback may still arrive multiple times in edge cases (e.g., manual replays from operations).
tipAlways make your handler idempotent. A callback may arrive multiple times. Deduplicate on
(id, type, status)and never apply business effects twice.
Cascading
When cascading is enabled and triggered for an order, only the callback of the last payment attempt (the one that determines the final order status) is sent. Intermediate decline callbacks are suppressed. The exception: the first attempt's callback is sent if it requires customer redirection.
Callback parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (UUID) | yes | Public payment ID generated by our system, unique value per attempt. |
order_number | string (≤255) | yes | Echoed order.number from your auth request. |
order_amount | float (XX.XX) | yes | |
order_currency | string (3–6) | yes | |
order_description | string (≤1024) | yes | |
order_status | string | yes | One of prepare, settled, pending, 3ds, redirect, decline, credit, refund, reversal, void, chargeback. |
type | string (≤36) | yes | See Event types. |
status | string (≤20) | yes | See Status values. |
reason | string (≤1024) | conditional | Decline reason. Present when status=fail. |
card | string (XXXXXX******XXXX) | optional | Masked card number. |
card_expiration_date | string (MM/YYYY) | optional | |
card_token | string | optional | Card token for purchase and debit operations. Returned when req_token=true was set; also returned in the get-status response. |
customer_name / customer_email / customer_country / customer_state / customer_city / customer_address | string | optional | Echoed customer details. |
customer_ip | string | yes | |
payee_name / payee_email / payee_country / payee_state / payee_city / payee_address | string | optional | Returned for transfer only. |
payee_card | string | optional | Returned for transfer only. |
crypto_network / crypto_address | string | optional | Crypto transactions only. |
recurring_init_trans_id | string (UUID) | optional | First transaction in the recurring chain. Returned when recurring was initialized. |
recurring_token | string | optional | Token to use for /payment/recurring. |
schedule_id | string | optional | Recurring schedule. |
vat_amount | float | optional | Returned when vat_calc=true. |
digital_wallet | string | optional | googlepay or applepay if used. |
pan_type | string | optional | dpan or fpan for digital wallets. |
exchange_rate, exchange_rate_base, exchange_currency, exchange_amount | mixed | optional | Returned when currency exchange was applied. |
connector_name* | string | optional | Internal connector name. |
rrn* | string | optional | Retrieval Reference Number from acquirer. |
arn* | string | optional | Acquirer Reference Number. |
approval_code* | string | optional | Acquirer approval code. |
brand* | string | optional | E.g., visa. |
payment_method* | string | optional | |
gateway_id* | string | optional | Identifiers from the acquirer`s gateway. |
extra_gateway_id* | string | optional | Identifiers from the acquirer`s gateway. |
merchant_name* | string | optional | Given in the admin panel |
mid_name* | string | optional | Given in the admin panel |
issuer_bank* | string | optional | BIN-derived data. |
issuer_country* | string | optional | BIN-derived data. |
extended_data* | string | optional | Custom key-value data |
custom_data | object | optional | Echoed custom_data from your auth request (JSON callback mode) or flattened form fields. |
merchant_key | string | optional | Merchant key (CLIENT_KEY). Selectable in the callback parameter list under Protocol Mapping — returned in the callback when selected, and in the get-status response when get status response is enabled there. |
checkout_payment_method* | string | optional | Payment method name in Checkout terminology (for example card, applepay). Also appended to success_url / cancel_url as payment_method when enabled. |
initiator* | string | optional | Card-On-File: initiator of the payment (Customer or Merchant). |
sequence* | string | optional | Card-On-File: One-off, Initial, or Subsequent. |
source* | string | optional | Card-On-File: Card, Network Token, Card On File, Internal Token, or Recurring Token. |
date | string (YYYY-MM-DD HH:MM:SS) | optional | Transaction time. |
hash | string | yes | Verify against Hash signature. |
* marks fields returned only when explicitly enabled in Protocol Mappings (Add Extended Data to Callback / Add Data to Callback). Fields without * that are marked optional are returned automatically in the scenarios described in their row (for example, recurring, digital-wallet, VAT, or currency-exchange fields), not through this Protocol Mapping toggle.
The set of parameters returned in the get-status response is configurable via Protocol Mapping (admin panel → Configuration → Protocol Mappings). The Card-On-File fields (initiator / sequence / source) are not sent by default; enable them per merchant in Protocol Mapping.
When the MID has Card-On-File support (card_on_file_support) enabled, a Purchase or Debit generates a Card-On-File: Payment Platform requests COF generation from the connector and stores the resulting card_token with the transaction. The merchant reuses that card_token in subsequent Purchase and Debit payments (including recurring); the initiator / sequence / source attributes above describe the Card-On-File context of each such payment.
3DS authentication attributes
When enabled under Protocol Mapping → Add Data to Callback, the callback carries the 3DS authentication result. These are populated when the payment went through an External MPI (stored as-is) or the connector-service / Checkout collector.
| Field | Description |
|---|---|
threeDSServerTransID | Transaction ID in the 3DS system. |
dsTransID | Directory server transaction ID. |
acsTransID | ACS (issuing bank) transaction ID. |
xid | Main transaction ID in 3DS v1.0. |
transStatus | Authentication status (Y success, N failure, C challenge, and so on). |
transStatusReason | Status reason code. |
eci | Electronic Commerce Indicator. |
protocolVersion | 3DS protocol version. |
authenticationFlow | Frictionless or Challenge. |
Browser metadata attributes
Also configurable under Protocol Mapping → Add Data to Callback, carrying the payer's browser data.
| Field | Example | Description |
|---|---|---|
browser_color_depth | 24 | Screen color depth in bits. |
browser_screen_height | 1080 | Screen height in pixels. |
browser_screen_width | 1920 | Screen width in pixels. |
browser_java_enabled | FALSE | Java support flag. |
browser_javascript_enabled | TRUE | JavaScript availability flag. |
browser_language | uk-UA | Language set in the browser. |
browser_timezone_offset | -180 | Minutes between UTC and local time. |
browser_user_agent | Mozilla/5.0 ... | User-Agent string. |
browser_accept_headers | text/html, ... | HTTP Accept header. |
browser_platform | Win32 | Payer's operating system. |
Reference handler (Node.js)
app.post("/checkout/callback",
express.urlencoded({ extended: false }), // application/x-www-form-urlencoded
async (req, res) => {
const cb = req.body;
// 1. Verify signature (constant-time)
if (!verifyCallback(cb, process.env.CHECKOUT_PASSWORD)) {
return res.status(401).end();
}
// 2. Idempotency
const eventKey = `${cb.id}:${cb.type}:${cb.status}`;
if (await cache.exists(eventKey)) return res.status(200).end();
// 3. Find the transaction in your DB
const tx = await db.tx.findById(cb.id);
if (!tx) return res.status(404).end();
// 4. Decide based on (type, status, order_status); see §6.4
const settlingTypes = ["sale", "capture", "recurring", "debit", "transfer", "credit"];
if (cb.status === "fail") {
tx.state = "decline";
tx.reason = cb.reason;
await db.tx.save(tx);
await notifyCustomerFailure(tx);
} else if (cb.status === "success" &&
settlingTypes.includes(cb.type) &&
cb.order_status === "settled") {
// Final success: fulfill the order
tx.state = "settled";
await db.tx.save(tx);
await fulfillOrder(tx);
} else if (cb.status === "success" &&
cb.type === "sale" &&
cb.order_status === "pending") {
// DMS authorization: funds held, awaiting capture
tx.state = "authorized";
await db.tx.save(tx);
// Trigger capture later or via API; do NOT fulfill yet.
} else if (cb.type === "refund" && cb.status === "success") {
tx.state = "refund";
await db.tx.save(tx);
} else if (cb.type === "void" && cb.status === "success") {
tx.state = "void";
await db.tx.save(tx);
} else if (cb.type === "chargeback") {
await openDispute(tx, cb);
} else if (cb.type === "reversal") {
tx.state = "reversal";
await db.tx.save(tx);
}
// 3ds / redirect / init / waiting / undefined: do not finalize, wait for next callback
await cache.set(eventKey, 1, "EX", 7 * 24 * 3600);
return res.status(200).end();
});
Examples
Successful sale
id=f0a51dfa-fc43-11ec-8128-0242ac120004&
order_number=order-1234&
order_amount=3.01&
order_currency=USD&
order_description=bloodline&
order_status=settled&
type=sale&
status=success&
card=411111****1111&
card_expiration_date=12/2022&
arn=74046106207016278609311&
rrn=789012345678&
approval_code=123456&
date=2022-07-05 09:22:09&
hash=6d8d440e25bdfc5288616ce567496948d2562852
Failed sale
id=1f34f446-fc45-11ec-a50f-0242ac120004&
order_number=order-1234&
order_amount=3.01&
order_currency=USD&
order_description=bloodline&
order_status=decline&
type=sale&
status=fail&
reason=Declined by processing.&
card=411111****1111&
card_expiration_date=12/2022&
date=2022-07-05 09:30:35&
hash=7f15d178e9b2c8507dea57f8ed1efddb9573fa6b
Successful refund
id=f0a51dfa-fc43-11ec-8128-0242ac120004&
order_number=order-1234&
order_amount=3.01&
order_currency=USD&
order_description=bloodline&
order_status=settled&
type=refund&
status=success&
date=2022-07-05 09:28:01&
hash=6d8d440e25bdfc5288616ce567496948d2562852
Crypto initialization (intermediary)
id=ac60a7e6-9abf-11ef-a549-0242ac120002&
order_number=order-1234&
order_amount=0.00000001&
order_currency=BTC&
order_description=Important+gift&
order_status=pending&
type=init&
status=success&
crypto_network=ERC20&
crypto_address=bc1ABIJKL1234abfjkmoPQRTVXY5678prsx&
date=2024-11-04+15%3A15%3A49&
hash=871a243619e4fd04db377cfbabe9fe07505febf5
Note:
type=initis the first callback for crypto deposits, sent before the customer transfers funds. It carries the destinationcrypto_addressandcrypto_network. A subsequent callback (e.g.,type=sale, status=successortype=sale, status=fail) delivers the final result once the transfer is observed on-chain.