Initial delivery + retry 1
First delivery on event. If 5xx or timeout, retry 30 seconds later.
Webhooks fire on every order state transition: received, printed, dispatched, failed. JSON over HTTPS, HMAC-SHA256 signed for verification, retry queue with exponential backoff up to 24 hours. Event reference, signature verification code samples, and retry / dead-letter policy below.
HMAC-SHA256 SIGNED · AT-LEAST-ONCE · 24h RETRY · DEAD-LETTER QUEUE
Subscribe to any subset on the PUT /v1/account/webhook
endpoint. Most integrations subscribe to all five.
| Event | Fires when | Typical action |
|---|---|---|
| order.received | Fabrixa accepted the order and assigned an order_id. Ack within seconds of POST. | Update internal order status to "in production"; surface to end customer. |
| order.printed | Reactive print step complete. Garment moves to cut-and-sew. Mid-production milestone. | Optional — some teams use this for end-customer "your order is being made" emails. |
| order.dispatched | Quality-checked, packaged, handed to carrier. Tracking number issued. | Update order status to "shipped"; trigger end-customer shipping notification with tracking URL. |
| order.delivered | Carrier confirmed delivery to recipient address. | Trigger post-purchase flows: review request, related-product upsell, etc. |
| order.failed | Order can’t complete — artwork unreachable, recipient address invalid, fabric out of stock for an extended period. | Open ticket / notify ops. Payload includes error.code for switch-on logic. |
Common envelope (event, order_id, order_ref, timestamp) plus an event-specific
data object. Consume the envelope first; switch on event for the data shape.
# order.dispatched payload — POST to your webhook URL POST /your-webhook-endpoint Content-Type: application/json X-Fabrixa-Event: order.dispatched X-Fabrixa-Signature: t=1715173928,v1=ab3c4... X-Fabrixa-Delivery-Id: del_94kQ7r2L { "event": "order.dispatched", "order_id": "ord_8e6f4b2a", "order_ref": "shopify-1042", "timestamp": "2026-05-15T11:32:08Z", "production_hub": "PT", "data": { "tracking": { "carrier": "DHL", "tracking_number": "JJD0123456789", "tracking_url": "https://dhl.com/track/JJD0123456789", "estimated_delivery": "2026-05-19" }, "recipient": { "name": "Lena Costa", "city": "Porto", "country": "PT" } } }
Verify the signature before processing. Use the raw request body — not a re-serialised JSON object — or the HMAC will mismatch. The signing secret is issued once when you set up the webhook URL; rotate via dashboard.
// Node.js / Express example const crypto = require("crypto"); app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => { const signature = req.headers["x-fabrixa-signature"]; const [tPart, v1Part] = signature.split(","); const timestamp = tPart.split("=")[1]; const received = v1Part.split("=")[1]; // Reject events older than 5 minutes (replay protection) if (Math.abs(Date.now() / 1000 - timestamp) > 300) return res.status(400).end(); const payload = `${timestamp}.${req.body.toString()}`; const expected = crypto .createHmac("sha256", process.env.FABRIXA_WEBHOOK_SECRET) .update(payload) .digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) { return res.status(401).end(); } // Signature valid — process the event const event = JSON.parse(req.body); handleEvent(event); res.status(200).end(); });
Equivalent samples for Python (Flask), PHP, Ruby, and Go are in the Postman collection’s scripts folder. timingSafeEqual matters — don’t use string equality, it’s vulnerable to timing attacks.
Webhook delivery is at-least-once. If your endpoint returns 5xx or times out (10s timeout), we retry on an exponential backoff schedule for up to 24 hours. After 24h of failure, the event lands in a dead-letter queue you can replay manually.
First delivery on event. If 5xx or timeout, retry 30 seconds later.
60s → 5min → 30min → 2h → 6h → 12h → 24h. Each retry waits longer.
Event moved to dead-letter queue. Replay manually via dashboard or POST /v1/webhooks/replay/:delivery_id.
The most operationally important event. Payload includes a stable error.code string — safe to switch on for automated routing, ticket creation, or end-customer messaging.
{
"event": "order.failed",
"order_id": "ord_8e6f4b2a",
"order_ref": "shopify-1042",
"timestamp": "2026-05-13T09:14:22Z",
"data": {
"error": {
"code": "artwork_unreachable",
"message": "GET on artwork_url failed (timeout 30s) after 3 attempts",
"retryable": true,
"context": {
"artwork_url": "https://cdn.yourbrand.com/art/drop-001.png"
}
}
}
}
# Common error.code values:
# artwork_unreachable — your artwork URL didn't respond
# artwork_invalid_format — couldn't parse the file (try PNG / vector PDF)
# artwork_resolution_low — DPI too low for the chosen product
# address_invalid — recipient address didn't validate
# fabric_unavailable — fabric base out of stock for >7 days
# payment_method_failed — billing on file declined
# compliance_flag — order flagged by compliance review
POST /v1/webhooks/test
sends a synthetic event of any type to your registered webhook URL.
Useful for testing your handler before placing real orders, or for verifying
a new endpoint after a deploy.
Sandbox webhooks deliver to your sandbox URL. Use a tunnelling tool (ngrok, Cloudflare Tunnel) to point sandbox webhooks at localhost during development.
curl -X POST $BASE_URL/v1/webhooks/test \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "event": "order.dispatched", "sample_order_id": "sample" }' # 200 OK { "delivery_id": "del_test_94kQ", "posted_to": "https://your-tunnel.ngrok.io/webhook", "status": "queued" }
One webhook URL per account, all event types fire to it. Filter on the
event
field in your handler. Set up via the integration guide.