Webhooks
When an order reaches a terminal state, AliX POSTs the whole order to your endpoint so you do not have to poll for it.
Setting one up
- Register your endpoint URL and its secret key in the AliX management portal. Sandbox and production endpoints are registered separately.
- Pass that secret as
webhookSecretKeywhen you create the order.
The webhook is chosen per order: the webhookSecretKey you pass at creation binds that order to
the registered endpoint, and an order created without one settles silently — you read its outcome
from orders/details. A key matching no registered
endpoint is rejected at creation with 13 WEBHOOK_SECRET_NOT_FOUND, and the order is not created.
When it fires
A callback fires when an order reaches a terminal state — SUCCESS or ERROR. The create response
already hands you the order object, and the states in between are readable at any time through
orders/details.
What you receive
An HTTP POST, Content-Type: application/json, no custom headers. The body is
the order object — the same shape orders/details returns, with an
id field added:
{
"id": 184392,
"externalOrderId": "acme-co-1042",
"type": "CASHOUT",
"fiatAmount": 5015000,
"paidAmount": 5000000,
"tokenTransfer": {
"currency": "USDT",
"network": null,
"price": 26150,
"amount": 191.778,
"address": null,
"txHash": null,
"memo": null
},
"bankTransfer": {
"bankAccountName": "NGUYEN VAN A",
"bankAccountNumber": "0123456789",
"bankName": "Vietcombank",
"bankCode": "VCB",
"contentPayment": "ALIX CASHOUT 1042",
"totalPayment": 5000000,
"qrUrl": null
},
"fees": { "systemFee": 5000, "processingFee": 10000 },
"status": "SUCCESS",
"descriptions": "Success",
"createdAt": "2026-07-14 10:32:07",
"expiresAt": "2026-07-14 17:47:07",
"signature": "K3tQ...=="
}
Verify the signature
The signature arrives as a field in the body. Rebuild the canonical string from four fields of the
payload, append your secretKey, and verify with AliX's public key — the same one you use for
response signatures:
externalOrderId|type|fiatAmount|status|<secretKey>
import express from 'express';
import {createVerify} from 'node:crypto';
import {readFileSync} from 'node:fs';
const alixPublicKey = readFileSync('alix-public.pem', 'utf8');
const SECRET_KEY = process.env.ALIX_SECRET_KEY;
const app = express();
app.post('/webhooks/alix', express.json(), (req, res) => {
const o = req.body;
const canonical = `${o.externalOrderId}|${o.type}|${o.fiatAmount}|${o.status}|${SECRET_KEY}`;
const verified = createVerify('RSA-SHA256')
.update(canonical)
.verify(alixPublicKey, o.signature, 'base64');
if (!verified) return res.sendStatus(401);
res.sendStatus(200); // acknowledge first, process after
void settleOrder(o.externalOrderId, o.status);
});
The signature authenticates the order's identity and outcome — externalOrderId, type,
fiatAmount, status. The other fields in the payload are not covered by it, which is one more
reason the pattern below reads the full order from
orders/details: treat the callback as a signed
notification, and take the data from the order detail.
Delivery model
Each callback is delivered once, at settlement. Build the receiver on four habits and it stays reliable:
- Verify, then acknowledge immediately. Return
200with an empty body before doing any processing work; process asynchronously. What you return does not affect the order itself. - Make the handler idempotent on
externalOrderId. - Confirm with
orders/details. The callback tells you the moment an order settles; the order detail is the record you reconcile against. - Keep a reconciliation sweep. Poll any order still non-terminal past its expected settlement time, so every order reaches a terminal state in your system whether or not its callback was received.