Skip to main content
A webhook lets AssetGullak notify your own systems the moment something happens — a device goes offline, a policy violation opens, a bulk import finishes — instead of your integration having to repeatedly poll the API and ask “did anything change yet?” You register a URL and choose which events matter to you. From then on, whenever one of those events happens, AssetGullak sends an HTTP POST to your URL with the details — signed, so you can verify it genuinely came from AssetGullak and wasn’t sent by someone who found your endpoint.

Real-time

Delivered within seconds of the event happening — no polling delay.

Signed

Every payload carries an HMAC signature you can verify before trusting it.

Retried automatically

A failed delivery is retried on a backoff schedule, not dropped silently.

The envelope

Every webhook delivery is a POST request with the same outer shape, regardless of which event fired:

Setting up a webhook

1

Create the webhook

POST /api/v1/webhooks with a URL and the event types you want to subscribe to:
The response includes a secret — this is the only time you’ll ever see it. Store it securely; there’s no “reveal secret” option later, only delete and recreate.
2

Verify your endpoint can receive it

Trigger a matching event (or wait for a real one) and confirm a request actually arrives at your URL, with the headers described below.
3

Verify the signature

Before trusting the payload, recompute the signature yourself and compare it against the X-AssetGullak-Signature header — see Verifying signatures.
Webhooks are available on the Enterprise plan. Creating a webhook on a plan that doesn’t include this feature returns a 403.

Verifying signatures

Every delivery includes two headers: The signature is computed over the raw request body — the exact bytes sent, before any JSON parsing — using your webhook’s secret as the HMAC key. Recompute it the same way on your end and compare.
Compare signatures using a constant-time comparison function, not == or ===. A naive string comparison leaks timing information that can, in principle, let an attacker guess a valid signature byte by byte. Both examples below use the correct constant-time approach.
Use the raw request body for verification — not a re-serialized version of the parsed JSON. Most web frameworks parse the body into an object before your handler runs; if you re-serialize that object to compute the signature, differences in key order or whitespace will produce a different signature than the one that was actually sent, and verification will fail even though the payload is genuine. Read the raw body first, verify, then parse.

Retries and delivery behavior

If your endpoint doesn’t respond with a 2xx status — or doesn’t respond at all within 10 seconds — the delivery is retried automatically: After the fifth attempt, the delivery is marked permanently failed and isn’t retried further. Your endpoint returning a 2xx at any point stops the retry sequence for that specific delivery. Each delivery attempt is logged and viewable via GET /api/v1/webhooks/{webhook_id}/deliveries — useful for diagnosing a missed event without needing to reproduce it live.

Event reference

device.online / device.offline
device.warningSame shape as above, plus current resource usage:
device.deleted
The payload is self-sufficient since the device won’t exist to look up afterward.
asset.created
asset.deleted
asset.status_changed
asset.reassignedprevious_assignee / new_assignee are polymorphic — either can be an employee, department, or location. previous_assignee is null for a genuinely first-time assignment; new_assignee is null when an asset is unassigned entirely. expected_return_date is set only for temporary assignments — null for a normal, permanent one, and always null when the event represents an unassignment.
asset.device_linked / asset.device_unlinked
asset.verifiedFired when IT staff directly marks an asset as physically verified — a standalone action distinct from the Physical Verification confirmation cycle (which is employee self-attestation via email). This event represents someone from IT personally confirming the asset in person.
command.completed / command.failedSame shape for both — exit_code carries the finer-grained success/failure signal. Full stdout/stderr aren’t included; fetch those via the API if you need them.
command.expiredNo exit_code — the command never ran.
command_batch.completedOne summary event per batch, not one event per device.
bulk_import.completed
subject is polymorphic — a violation can be about a device or an asset.policy_violation.opened / policy_violation.resolved
policy_violation.acknowledgedSame as above, plus who acknowledged it:
policy_violation.unacknowledgedSame shape as policy_violation.opened — acknowledged_by is no longer relevant once cleared.policy_violation.assigned
policy_violation.unassignedSame shape as policy_violation.opened — no assigned_to field, since there’s no assignee left to describe.policy_violation.fix_attemptedFired when someone claims they’ve addressed a violation — this is not the same as resolution. Resolution is always verified automatically by the policy engine on its next evaluation pass, never claimed directly by a person; this event just marks that someone believes they’ve fixed it and the system should double check. If the claim turns out to be right, a policy_violation.resolved event follows once the policy engine confirms it; if not, the violation simply stays open.

Managing webhooks

A company can have up to 10 webhooks active at once. Creating or managing webhooks requires an Owner or Admin role.
Webhook URLs must resolve to a genuine public address. URLs pointing at localhost, private IP ranges, or other internal/reserved addresses are rejected at creation and update time.
  • Locations — assigned_location and last_seen_location in device event payloads both reference locations
  • Location signals — how last_seen_location gets populated automatically