Skip to main content

Webhooks

When an order reaches a terminal state, we POST the whole order to your endpoint so you do not have to poll for it.

Setting one up​

  1. Register your endpoint URL and its secret key in the AliX management portal. Sandbox and production endpoints are registered separately.
  2. Pass that secret as webhookSecretKey when 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:

JSON
{
"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 our public key — the same one you use for response signatures:

externalOrderId|type|fiatAmount|status|<secretKey>
JavaScript
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 sit outside the signature, so the pattern below treats the callback as a signed notification and reads the full order from orders/details.

Delivery model​

Each callback is delivered once, at settlement. A reliable receiver does four things:

  • Verify, then acknowledge immediately. Return 200 with 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.