API tokens and webhooks
Connect your workspace to outside systems in two directions: mint a scoped Bearer token so a client can read from the REST API, and subscribe a URL so OnTrackio pushes events to it. Both are admin-only and both leave an audit trail.
The two surfaces are independent. A token authenticates inbound API calls; a webhook subscription is an outbound delivery target. You can use either on its own.
:::note Before you begin
- An admin account on your workspace — the
admin,it-admin, orsuper-adminrole reaches Admin → API tokens and Admin → Settings. - Your workspace slug. The REST API lives at
https://<slug>.app.ontrackio.com/api/; replace<slug>with yours. - For a webhook, a public HTTPS endpoint that can receive a
POST. Private, loopback, and cloud-metadata addresses are rejected — see Webhook delivery. - The canonical, always-current API contract is the OpenAPI spec rendered at
https://<slug>.app.ontrackio.com/api/docs. Treat this page as a guide to managing credentials and subscriptions, and the spec as the source of truth for endpoints and payloads. :::
Part 1 — API tokens
Tokens are Bearer credentials for the REST API at /api/. Each one carries an owner, a set of abilities (its scope), an optional expiry, and an optional IP allowlist. OnTrackio stores only the SHA-256 hash of a token — the plaintext is shown once, at creation, and can never be retrieved again.
Mint a token
-
Open Admin → API tokens.
-
Under Mint new token, fill in the fields below.
Field Required Default Notes Token name Yes — Free text, max 120 characters. For your own reference, for example CI integrationorDatadog metrics.Owner Yes The current admin The token inherits the owner's role. The owner must be an active user. Deactivating the owner revokes the token automatically. Abilities (scope) Yes None Tick at least one. Each ability grants one slice of the API — see Abilities. Use Select all read+write to tick every scope except *.Expires at No 6 months out A date between tomorrow and one year out. Leave blank for a non-expiring token. IP allowlist (CIDR) No None Comma- or newline-separated CIDR ranges. When set, requests from any other IP get a generic 401. See Restrict a token to an IP range. -
Select Mint token.
-
Copy the plaintext from the green banner now. It starts with
itam_and is shown only on this page load; refreshing hides it for good.
:::warning The plaintext is shown once OnTrackio keeps only the SHA-256 hash. If you lose the plaintext, you can't recover it — revoke the token and mint a new one. :::
:::tip Set an expiry Leaving a token non-expiring is flagged as an audit finding under SOC 2 and ISO 27001 A.9.4.3. Prefer a dated expiry and rotate before it lapses. The form pre-fills six months for this reason. :::
Use a token
Send the plaintext in the Authorization header on every request:
curl https://<slug>.app.ontrackio.com/api/v1/hardware \
-H "Authorization: Bearer itam_<your-token>"
The guard resolves the token, checks expiry, owner status, IP, and the required ability, then runs the request as the token's owner.
| Response | Meaning |
|---|---|
401 Missing bearer token | No Authorization: Bearer … header was sent. |
401 Invalid or expired token | The token is unknown, expired, its owner is inactive, or the request IP is outside the token's allowlist. The reasons are deliberately indistinguishable. |
403 Token lacks ability: <ability> | The token is valid but its scope doesn't cover this endpoint. Mint a token with the right ability. |
Abilities
Abilities follow a resource:action convention. Tick exactly the scopes a client needs — a token grants nothing unless at least one ability is selected.
| Ability | Grants |
|---|---|
hardware:read | Read hardware records |
hardware:write | Create, update, and delete hardware |
software:read | Read software licenses and assignments |
software:write | Create and update software licenses |
users:read | Read the user directory |
users:write | Create, update, and delete users |
audit:read | Read the audit log and export endpoints |
webhooks:read | Read webhook subscriptions and deliveries |
webhooks:write | Create, update, and delete webhook subscriptions |
scim | SCIM 2.0 endpoints — IdP joiner / mover / leaver push |
* | Unrestricted full API access — use with care |
:::note scim is separate from users:*
The users:read / users:write abilities power the general REST API at /api/v1/users (scripts, reporting tools). The scim ability powers the SCIM 2.0 protocol at /scim/v2/ for an identity provider. Mint a dedicated scim token so you can rotate the IdP integration without disturbing other clients. See SCIM provisioning.
:::
Restrict a token to an IP range
To pin a token to your fixed-egress IPs, enter one or more CIDR ranges in IP allowlist (CIDR) when you mint it. Both IPv4 and IPv6 are accepted.
203.0.113.0/24
198.51.100.42/32
2001:db8::/32
A request from outside the list is rejected with the same generic 401 as a malformed token — an attacker who steals the token but isn't behind your egress IP gets no signal that the token is otherwise valid. Leave the field blank for no restriction.
An invalid CIDR is rejected at submission with an inline error, so a typo never silently locks out your client. Validate the ranges against your egress before relying on them.
Review and revoke tokens
The Active tokens table lists every token with its owner, abilities, last-used time and IP, and expiry. An expired token shows an Expired badge; a non-expiring one shows an amber Never.
To revoke a token, select Revoke in its row and confirm. Revocation is immediate — any client using that token fails authentication on its next request. Both minting and revoking are written to the audit log under the api channel.
:::note Two places, one token store A legacy token form also lives under Admin → Settings → API & webhooks. It writes to the same store, so a token minted there appears in Admin → API tokens and vice versa. The dedicated Admin → API tokens page is canonical — it adds the per-ability scope picker and the IP allowlist. Use it for new tokens. :::
Part 2 — Outbound webhooks
A webhook subscription tells OnTrackio to POST a signed JSON payload to your URL whenever a subscribed event happens. Subscriptions live in the API & webhooks tab of Settings.
Subscribe a URL
-
Open Admin → Settings, then select the API & webhooks tab.
-
Under Webhook subscriptions, fill in the fields below.
Field Required Default Notes Subscription name Yes — Free text, max 120 characters. For your reference. URL Yes — A valid public HTTPS endpoint, max 500 characters. Receives the POST.Events Yes — Select at least one event, or *for all. Hold Cmd / Ctrl to select several. See Events. -
Select Subscribe.
A signing secret is generated automatically for the subscription and used to sign every delivery. New subscriptions are active immediately.
:::warning Your endpoint must be publicly reachable OnTrackio refuses to deliver to loopback, private (RFC 1918), link-local, or cloud-metadata addresses, and to non-HTTP(S) schemes — this stops a pasted URL being used to probe internal infrastructure. A subscription to such a URL records the rejection as a failed delivery. :::
Events
Subscribe to specific events, or to * to receive everything (including events added later).
| Event | Fires when |
|---|---|
hardware.created | A hardware asset is created |
hardware.updated | A hardware asset is edited |
hardware.assigned | A hardware asset is assigned to a person |
hardware.recovered | A hardware asset is recovered from a person |
asset_request.created | An employee submits an asset request |
asset_request.approved | A request is approved |
asset_request.rejected | A request is rejected |
asset_request.fulfilled | A request is fulfilled |
software.assigned | A software license seat is assigned |
software.revoked | A software license seat is revoked |
user.offboarded | A user is offboarded |
* | Any of the above, current and future |
How webhook delivery works
For each active subscription that matches the event, OnTrackio sends one POST. Delivery is best-effort and never blocks the action that triggered it — a failing endpoint won't break an assignment or a request.
| Property | Value |
|---|---|
| Method | POST |
Content-Type | application/json |
X-Itam-Event | The event name, for example hardware.assigned |
X-Itam-Signature | Hex-encoded HMAC-SHA256(secret, raw_body) — verify this to confirm authenticity |
User-Agent | OnTrackio-ITAM-Webhook/1.0 |
| Timeout | 8 seconds |
| Retries | None — each event is delivered once; a non-2xx or timeout is recorded as failed |
Every delivery — success or failure — is logged with its response status and body. The subscription's row shows the last delivery time, a running total, and a status badge: a green 2xx badge, a red status code on failure, Paused when inactive, or No deliveries before the first attempt.
Payload shape
The body is a JSON object with the event, a timestamp, and an event-specific data object:
{
"event": "hardware.assigned",
"occurred_at": "2026-06-07T10:15:30+00:00",
"data": {
"hardware_id": 412,
"asset_tag": "OT-000412",
"user_id": 87
}
}
Verify the signature
Recompute the HMAC over the exact raw request body with your subscription's secret and compare it to X-Itam-Signature. Reject the request if they differ.
$expected = hash_hmac('sha256', $rawBody, $secret);
if (! hash_equals($expected, $request->header('X-Itam-Signature'))) {
abort(401);
}
:::tip Compare against the raw body Sign the bytes exactly as received, before any JSON parsing or re-encoding. Re-serializing the payload changes whitespace and key order, which breaks the comparison. :::
Test, pause, and delete a subscription
- Test sends a synthetic
test.pingdelivery to that one endpoint so you can confirm connectivity without waiting for a real event. It targets only the selected subscription, not every subscriber. Save any URL change first. - A subscription with no active events stops receiving deliveries and shows a Paused badge.
- Delete removes the subscription. Past deliveries are no longer reachable from the UI.
Troubleshooting
| Symptom | What to do |
|---|---|
API call returns 401 Missing bearer token | Add the Authorization: Bearer <token> header. The value is the plaintext shown once at mint time, starting with itam_. |
API call returns 401 Invalid or expired token | The token is unknown, expired, its owner was deactivated, or the request IP is outside the token's allowlist. Check the expiry and owner status in Admin → API tokens, and confirm the calling IP is in any CIDR allowlist. |
API call returns 403 Token lacks ability | The token's scope doesn't cover the endpoint. Mint a token that includes the required ability — for example hardware:write to create assets. |
| Lost the token plaintext | It's unrecoverable by design. Revoke the token and mint a new one. |
| Webhook never arrives | Confirm the subscription is active and subscribed to the right event (or *). Use Test to probe connectivity, then check the status badge and last-delivery total. |
| Webhook status badge shows a red code or No deliveries | The endpoint returned a non-2xx, timed out (8 s), or the URL was rejected as private/loopback/metadata. Confirm the endpoint is public HTTPS and responds within the timeout. There is no automatic retry. |
| Signature check fails on your side | Recompute HMAC-SHA256 over the raw body with the subscription's secret, hex-encoded, and compare to X-Itam-Signature. Don't parse and re-serialize the body first. |
| Token appears that you didn't mint here | It was likely created via the legacy form under Settings → API & webhooks, which shares the same store. Manage it from Admin → API tokens. |