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.

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

JSON
{
"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-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 — 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 PROCESSING past 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.