Webhooks
AliX POSTs to your endpoint when an order reaches a terminal state, so you do not have to poll.
See Order lifecycle callback for the full payload schema.
Setting one up
- Register the endpoint URL and its secret in the AliX management portal.
- Pass that secret as
webhookSecretKeywhen you create an order.
The webhook is chosen per order: an order created without a webhookSecretKey settles silently,
read through the order endpoint. A key that matches no registered endpoint
is rejected at create time with WEBHOOK_SECRET_NOT_FOUND.
What you receive
{
"event": "order.success",
"orderId": "acme-1042",
"occurredAt": "2026-07-06T05:00:12.000Z",
"data": { "orderId": "acme-1042", "status": "SUCCESS", "...": "full order snapshot" }
}
A callback fires when an order reaches a terminal state — order.success or order.failed; while
it is PROCESSING, the order endpoint reads its state at any time. data is the same shape the
order endpoint returns.
Verify the signature
Two headers come with the request:
| Header | |
|---|---|
X-Alix-Signature | Base64 RSA-SHA256 signature. |
X-Alix-Timestamp | Unix seconds when the callback was signed. Part of the signed string. |
The signed string is the timestamp, a literal dot, and the raw request body:
<X-Alix-Timestamp> + "." + <raw request body>
Verify it with the public key AliX issued you, and reject stale timestamps. Verify against the raw bytes — if your framework parses the JSON and you re-serialize it, key order or whitespace can change and the signature will not match.
import express from 'express';
import {createVerify} from 'node:crypto';
import {readFileSync} from 'node:fs';
const alixPublicKey = readFileSync('alix-public.pem', 'utf8');
const MAX_SKEW_SECONDS = 300;
const app = express();
// express.text — not express.json. The signature covers the raw body.
app.post('/webhooks/alix', express.text({type: 'application/json'}), (req, res) => {
const signature = req.get('X-Alix-Signature');
const timestamp = req.get('X-Alix-Timestamp');
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > MAX_SKEW_SECONDS) {
return res.sendStatus(400);
}
const verified = createVerify('RSA-SHA256')
.update(`${timestamp}.${req.body}`)
.verify(alixPublicKey, signature, 'base64');
if (!verified) return res.sendStatus(401);
const event = JSON.parse(req.body);
res.sendStatus(200); // acknowledge first, process after
void settleOrder(event.orderId, event.data.status);
});
Delivery model
Each callback is delivered once, at settlement. Respond with any 2xx to acknowledge, and build
the receiver on the habits that keep any webhook integration reliable:
- Make your handler idempotent, keyed on
orderId. Acknowledge fast and do the work asynchronously. - Treat the order endpoint as the source of truth. Keep a reconciliation job that polls
the order for anything still
PROCESSINGpast its expected settlement time, so every order reaches a terminal state in your system whether or not its callback was received.
Testing
On the sandbox you can fire a callback on demand by forcing an order to a terminal outcome — see Sandbox testing.