Skip to main content

Webhooks

We POST 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 the order is PROCESSING, you can read its state from the order endpoint at any time. data has 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 we 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. A reliable receiver does two things:

  • 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.