Skip to main content
A Fabrixa webhook payload on screen — signed JSON order event, against woven textile
Developers · Webhooks reference

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.

HMAC-SHA256 · order.created / order.updated · 10-retry delivery

Request headers

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.

Incoming headers
{
  "content-type":        "application/json",
  "x-webhook-topic":     "order.updated",
  "x-webhook-signature": "YmEwNjBhMGMy...MAxMw=="
}
Webhook events

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.

order.created

A new order is placed.

Triggered when an order is created in Fabrixa — either via an API request or directly in the platform.

order.updated

An existing order changes.

Triggered when order details change — for example the order status or the fulfilment status moving forward.

Statuses

What the status fields can say.

The payload carries both an order status and a fulfillment_status. Switch on these to drive your own order state.

Order statuses
  • 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.
Fulfilment statuses
  • 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.
Payload

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.

POST body — order.updated
{
  "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" }
        }
      ]
    }
  ]
}
Verify the signature

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.

Raw PHP
$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');
}
Laravel
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…
}
Delivery & response

Retries, and the response we expect.

Retries

Up to 10 attempts, then disabled

A 2xx (e.g. 200) or a 301 / 302 counts as success. Any 4xx, 5xx or timeout triggers a retry — up to 10 total. After the tenth failure the webhook is deactivated.

Response

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.

Build your handler

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.