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.
An order created without a webhookSecretKey gets no callback. 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" }
}
Only terminal events are delivered — order.success and order.failed. Nothing fires while
an order is still PROCESSING. 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 — there is no second delivery
void settleOrder(event.orderId, event.data.status);
});
Delivered once, never retried
Respond with any 2xx to acknowledge. There is no retry: if your endpoint is down, times out,
or 500s, that callback is gone.
Two consequences, and neither is optional:
- 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. The webhook is an optimization over polling, not a replacement for it.
Testing
On the sandbox you can fire a callback on demand by forcing an order to a terminal outcome — see Sandbox testing.