Skip to main content

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

  1. Register the endpoint URL and its secret in the AliX management portal.
  2. Pass that secret as webhookSecretKey when 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

JSON
{
"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-SignatureBase64 RSA-SHA256 signature.
X-Alix-TimestampUnix 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.

JavaScript
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 PROCESSING past 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.