Webhooks
Get notified the moment a work order changes — signed, and retried until delivered.
Instead of polling, subscribe to events and we’ll POST them to your endpoint as they happen. Create a subscription with the webhooks:manage scope:
curl -X POST "https://api.facilityopsiq.com/api/v1/webhooks" \
-H "Authorization: Bearer wo_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Prod sync",
"url": "https://example.com/hooks/facilityopsiq",
"events": ["record.created", "record.status_changed", "record.completed"]
}'The response includes a secret once — store it to verify signatures.
Events
record.created · record.status_changed · record.assigned · record.completed · record.closed · record.proof_delivered (a verifiable proof is now available) · asset.updated.
Delivery format
Each delivery is a POST with an X-Webhook-Event header and this body:
{
"event": "record.status_changed",
"payload": { "recordId": "WO-000188", "from": "ASSIGNED", "to": "IN_PROGRESS" },
"timestamp": "2026-07-27T12:00:00.000Z"
}Verifying signatures
We sign the raw request body with your subscription secret (HMAC-SHA256) and send it as X-Webhook-Signature: sha256=. Recompute it over the raw body and compare with a constant-time check before trusting the payload:
import { createHmac, timingSafeEqual } from 'crypto'
function verify(rawBody, header, secret) {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')
const a = Buffer.from(header || ''), b = Buffer.from(expected)
return a.length === b.length && timingSafeEqual(a, b)
}Retries
If your endpoint returns a non-2xx or times out (10s), we retry with exponential backoff — up to 4 attempts — on network errors, 429, and 5xx. A 4xx (other than 429) is treated as a permanent rejection and not retried. Respond 2xx quickly and process asynchronously; make your handler idempotent, since a delivery can arrive more than once.