Skip to content

Webhooks and Audit

Send a POST to /api/webhooks. Requires the webhooks:manage permission.

Terminal window
curl -X POST http://localhost:4000/api/webhooks \
-H "content-type: application/json" \
-H "x-api-key: dev-staff-key" \
-d '{
"url": "https://your-service.example.com/hooks/commerce",
"secret": "whsec_your_random_secret_string",
"events": ["orders.create", "orders.statusChange", "inventory.afterAdjust"]
}'
FieldTypeDescription
urlstringThe HTTPS URL that receives webhook payloads. Must be HTTPS in production.
secretstringA shared secret used to sign payloads. Store this securely on your receiving server.
eventsstring[]Event types to subscribe to.

The response returns the created webhook record including its id. Delete a webhook with DELETE /api/webhooks/:id. List all webhooks with GET /api/webhooks.

14 event types are available:

orders.create, orders.statusChange, catalog.create, catalog.update, catalog.delete, inventory.afterAdjust, customers.create, customers.update, pricing.create, pricing.update, promotions.create, promotions.update, fulfillment.create, cart.afterAddItem

Webhook payloads are delivered asynchronously through the background job queue. Failed deliveries are retried up to 5 times with exponential backoff. The job queue must be running (jobs.autorun.enabled: true) for webhooks to be delivered.

Every webhook delivery includes an x-commerce-signature header containing an HMAC-SHA256 signature of the request body, computed using the shared secret.

webhook-handler.js
const crypto = require("crypto");
function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
}
app.post("/hooks/commerce", (req, res) => {
const rawBody = req.rawBody;
const signature = req.headers["x-commerce-signature"];
if (!verifyWebhookSignature(rawBody, signature, "whsec_your_secret")) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(rawBody);
// process event...
res.status(200).send("OK");
});

Use crypto.timingSafeEqual to prevent timing attacks. Compare raw hex strings — not base64 or other encodings.

{
"id": "evt_01HQ...",
"type": "orders.create",
"timestamp": "2026-03-15T10:30:00.000Z",
"data": {
"id": "order-uuid",
"orderNumber": "ORD-2026-000042",
"status": "pending",
"grandTotal": 4999,
"currency": "USD"
}
}

The engine records an audit entry for every state-changing operation across all modules (orders, catalog, inventory, customers, pricing, promotions, fulfillment, cart, webhooks). Audit logging is enabled by default and requires no configuration.

Both audit endpoints require the audit:read permission.

Terminal window
curl "http://localhost:4000/api/audit?entityType=order&from=2026-03-01&limit=50" \
-H "x-api-key: dev-staff-key"

Query parameters:

ParameterDescription
entityTypeFilter by entity type (e.g., order, catalog, customer)
entityIdFilter to a specific entity UUID
eventFilter by event name (e.g., orders.create)
actorIdFilter by actor who triggered the event
fromStart of time range (ISO 8601)
toEnd of time range (ISO 8601)
limitMax results (default 50, max 100)
Terminal window
curl "http://localhost:4000/api/audit/order/550e8400-e29b-41d4-a716-446655440000" \
-H "x-api-key: dev-staff-key"

Returns all audit entries for the specified entity, ordered by timestamp descending. Accepts the same from, to, and limit parameters.