Every event. Every payload. Signed.
Fabrixa pushes order events to your endpoint in real time. Your server exposes one public POST endpoint; we send the full order body with an HMAC-SHA256 signature and the event topic in the headers. Verify the signature, return 200 fast, process asynchronously.
Three headers identify and verify the request.
Every webhook POST carries the same three headers. Use x-webhook-topic to route the event and x-webhook-signature to verify it before you trust the body.
content-type is always application/json. x-webhook-signature is the base64 HMAC-SHA256 of the raw body. x-webhook-topic is the event name.
{
"content-type": "application/json",
"x-webhook-topic": "order.updated",
"x-webhook-signature": "YmEwNjBhMGMy...MAxMw=="
}
Two topics today.
Each webhook is triggered by a specific x-webhook-topic value. Both carry the full order object, so a single handler can switch on the topic.
A new order is placed.
Triggered when an order is created in Fabrixa — either via an API request or directly in the platform.
An existing order changes.
Triggered when order details change — for example the order status or the fulfilment status moving forward.
What the status fields can say.
The payload carries the order status as well as a separate fulfillment_status. Switch on these to drive your own order state.
- Completed — shipped or picked up and receipt confirmed.
- Canceled — payment was cancelled; the transaction did not complete.
- On hold — the order is temporarily blocked.
- Imported — the order was imported into the platform.
- Unfulfilled — not yet prepared or sent.
- Partially fulfilled — some items processed or shipped, others pending.
- Scheduled — planned and scheduled for processing.
- Rejected — the fulfilment request was declined.
- Fulfilled — fully processed and delivered or made available.
What the body looks like.
The full order.updated payload, exactly as the API reference documents it — the order, its statuses, and the rows with variant, product and source detail.
{
"id": 23069,
"number": "1250211835",
"comments": null,
"is_archived": false,
"status": "imported",
"fulfillment_status": "unfulfilled",
"purchased_at": "2025-04-17T16:17:21.000000Z",
"created_at": "2025-04-17T16:17:23.000000Z",
"updated_at": "2025-04-18T07:43:52.000000Z",
"rows": [
{
"id": 27954,
"quantity": 1,
"client_barcode": "1250211835",
"fulfillment_status": "unfulfilled",
"variant": {
"id": 293457,
"name": "Sherpa fleece deken",
"subtitle": "100x150",
"SKU": "SFD787231",
"product": {
"id": 5319, "name": "Sherpa fleece deken", "subtitle": "Sherpa fleece deken"
}
},
"sources": [
{
"type": "print",
"url": "https://storage.googleapis.com/fabrixa-api/…/120002795400.pdf",
"properties": { "fill_style": "contain" }
}
]
}
]
}
Recompute the HMAC, compare, then trust the body.
Recalculate the HMAC-SHA256 of the raw request payload with your secret key, base64-encode it, and compare to x-webhook-signature. Always use a constant-time comparison (hash_equals) to avoid timing attacks.
$payload = file_get_contents('php://input'); $secret = 'your-secret-key'; $expected = base64_encode( hash_hmac('sha256', $payload, $secret, true) ); $received = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? ''; // constant-time compare if (!hash_equals($expected, $received)) { http_response_code(403); exit('Invalid signature'); }
public function handle(Request $request)
{
$secret = 'your-secret-key';
$payload = $request->getContent();
$expected = base64_encode(
hash_hmac('sha256', $payload, $secret, true)
);
$received = $request->header('x-webhook-signature');
if (!hash_equals($expected, $received)) {
abort(403, 'Invalid signature');
}
// Continue processing…
}
Retries, and the response we expect.
Up to 10 attempts, then disabled
Any 2xx response (e.g. 200) counts as success, as does a 301 or 302 redirect. Any 4xx, 5xx or timeout triggers a retry — up to 10 total. After the tenth failure the webhook is deactivated.
Return 200 fast, process async
Acknowledge with 200 OK as quickly as possible, then handle the payload asynchronously via a background job or queue. A slow endpoint can be treated as a failure even if it eventually succeeds.
Webhooks, answered
How do I verify a Fabrixa webhook signature?
Recompute the HMAC-SHA256 of the raw request body using your secret key, base64-encode the result, and compare it to the x-webhook-signature header with a constant-time comparison such as hash_equals. Fabrixa provides worked examples in raw PHP and Laravel, and any mismatch should be rejected before the payload is processed.
What webhook events does Fabrixa send?
Fabrixa sends two events, identified by the x-webhook-topic header: order.created, triggered when an order is placed via the API or directly in the platform, and order.updated, triggered when the order status or fulfilment status changes. Both carry the full order object, so one handler can switch on the topic.
How many times does Fabrixa retry a failed webhook?
Fabrixa retries a webhook delivery up to 10 times when the response is not a 2xx, 301, or 302. Any 4xx, 5xx, or timeout counts as a failure and triggers a retry; after the tenth failed attempt, the webhook is deactivated, so the endpoint should return 200 quickly and process the payload asynchronously.
What headers does a Fabrixa webhook request include?
Every webhook POST carries three headers: content-type, always application/json; x-webhook-topic, naming the event such as order.updated; and x-webhook-signature, the base64-encoded HMAC-SHA256 signature of the raw body. Use the topic to route the event and the signature to verify it before trusting the payload.
What order statuses appear in a Fabrixa webhook payload?
The payload carries a status field with four values — Completed, Canceled, On hold, and Imported — and a separate fulfillment_status field with five values: Unfulfilled, Partially fulfilled, Scheduled, Rejected, and Fulfilled. A single order.updated event can report a change in either field.
Wire up your webhook endpoint.
The integration guide walks through registering your endpoint and handling the order lifecycle end to end. The full API reference has the complete payload schema.


