# Guide to 1Password Device Trust (Kolide) Webhooks: Features and Best Practices

1Password Device Trust, formerly Kolide, checks that laptops meet your security policy before their users can sign in through SSO. It runs checks on each device, raises issues such as a disabled firewall or an outdated OS, and blocks sign-in until the user fixes them or is granted an exemption. Its webhooks push those device, issue, sign-in and admin events to your own systems, so a ticketing tool, CMDB or SIEM can stay current without polling the API.

Here you'll find the 13 Device Trust events, how to verify a signature that arrives in the `Authorization` header, what the endpoint identifier header is for, and how to work around an undocumented delivery policy.

## What are 1Password Device Trust (Kolide) webhooks?

Admins with Full Access register webhook endpoints in the Device Trust admin console and pick which events each one receives. Every delivery is a JSON `POST` with the same four top-level fields: `event`, `id`, `timestamp` and `data`. Each endpoint has its own signing secret and its own static identifier.

The product still lives on `kolide.com` domains, and payload URLs point at `api.kolide.com`. Delivery logs for each endpoint are kept for six months in the console.

## 1Password Device Trust (Kolide) webhook features

| Feature | Details |
| --- | --- |
| Signature | hex HMAC-SHA256 |
| Signature header | `authorization` |
| Body | JSON |
| Event routing | Body field `event` |

Also from the [Device Trust webhook documentation](https://support.1password.com/device-trust-webhooks/):

* `X-Kolide-Webhook-Identifier` is a static value per endpoint, shown next to the secret.
* Rotating the secret also changes the identifier.
* Delivery logs are kept for six months.
* Retries, timeouts, accepted status codes and source IPs are not documented.

## Events and payloads

| Event routing | Body field `event` |

These are the 13 documented event types. Handle the ones you need and log the rest.

This is the `admin_users.created` sample from the [webhook samples library](https://github.com/hookdeck/webhook-samples/blob/main/providers/kolide/latest/admin_users.created.json):

```json
{
  "event": "admin_users.created",
  "id": "01HDW1V4TDE1B3KCAMR8V7Z4S5",
  "timestamp": "2024-06-21T21:28:17Z",
  "data": {
    "user_id": 1,
    "name": "Fred Flintstone",
    "email": "fred@theflintstones.com",
    "access": "Admin",
    "admin_user_url": "https://api.kolide.com/admin_users/1"
  }
}

```

`data` changes shape with each event. `device_trust.status_changed` carries `device_id`, `device_name`, `device_status` and `device_url`. `issues.new` and `issues.resolved` carry the issue, the device and the check that raised it, and `title` can be `null` on a new issue. The test event's `data` is just `{ "message": "This is a test webhook event" }`. Most objects include a URL on `api.kolide.com` if you need the full record.

## Setting up 1Password Device Trust (Kolide) webhooks

1. In the admin console, go to **Settings > Developers > Webhooks** and add a webhook. You need Full Access.
2. Enter your endpoint URL, for example `https://example.com/webhooks/kolide`.
3. Subscribe to the events you'll handle, such as `device_trust.status_changed`, `issues.new`, `issues.resolved` and `auth_logs.failure`.
4. Open the endpoint and choose **Reveal Secret**. Store the secret as `KOLIDE_WEBHOOK_SECRET`, used as plain text.
5. Send the test event and check that your endpoint verifies it.

For Hookdeck, create a source of the 1Password Device Trust (Kolide) type with the revealed secret as its verification secret, and register the source URL in step 2. To work locally:

```bash
npx hookdeck-cli listen 3000 kolide --path /webhooks/kolide

```

## Securing 1Password Device Trust (Kolide) webhooks

The `Authorization` header holds a bare hex HMAC-SHA256 of the raw body, keyed with the endpoint's secret. It isn't a bearer token: compare the whole value as one digest and don't strip a scheme. See [verifying webhook authenticity](https://support.1password.com/device-trust-webhooks/#verifying-webhook-authenticity).

### Node.js verification

```javascript
const crypto = require('node:crypto');

function verifySignature(rawBody, headers, secret) {
  if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
  const signature = headers['authorization'];
  if (typeof signature !== 'string') return false;
  const key = Buffer.from(secret, 'utf8');
  const message = rawBody;
  const candidates = [signature];
  const expected = crypto.createHmac('sha256', key).update(message).digest();
  return candidates.some((candidate) => {
    if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
    const received = Buffer.from(candidate, 'hex');
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });
}

```

`X-Kolide-Webhook-Identifier` can screen traffic at a web application firewall, but anyone who sees one request learns it, so the HMAC check above is what proves authenticity.

This handler stores the event by `id` and acts on blocked devices from a worker:

```javascript
const express = require('express');

const app = express();

app.post('/webhooks/kolide', express.raw({ type: '*/*' }), async (req, res) => {
  const secret = process.env.KOLIDE_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500);
  if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);

  let parsed;
  try {
    parsed = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return res.sendStatus(400);
  const { event, id, timestamp, data } = parsed;
  try {
    if (event === 'webhook.test') return res.sendStatus(200);

    const isNew = await storeDeviceTrustEvent(id, event, timestamp, data);
    if (isNew && event === 'device_trust.status_changed' && data.device_status === 'blocked') {
      await queueBlockedDeviceAlert(data.device_id, data.device_name);
    }
  } catch {
    return res.sendStatus(503);
  }

  res.sendStatus(200);
});

```

`storeDeviceTrustEvent` returns `false` for an `id` it already holds, so a repeated delivery doesn't raise a second alert.

The `timestamp` field isn't signed, so don't build a freshness check from it. Hookdeck's Device Trust source verifies the same `Authorization` HMAC without a time limit, responds `200`, and records a mismatch as failed verification.

> **Make 1Password Device Trust (Kolide) webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) verifies each Device Trust event, keeps a full history alongside the console's delivery log, and retries delivery to your SIEM or ticketing system on a schedule you set.

## 1Password Device Trust (Kolide) webhook limitations and pain points

### Retry behaviour is undocumented

**The Problem:** You can't tell whether a missed `device_trust.status_changed` or `auth_logs.failure` will be sent again.

**Why It Happens:** The documentation covers events, headers and signing, but not retries, timeouts or which status codes count as success.

**Workarounds:**

* Return `200` quickly and do the work afterwards.
* After an outage, check the endpoint's delivery log in the console and reconcile devices and issues through the API.

**How Hookdeck Can Help:** Hookdeck accepts each event as it arrives and manages delivery to your service with retries you configure, so Device Trust's own retry behaviour matters less.

### The signature travels in `Authorization`

**The Problem:** Infrastructure in front of your receiver can drop, rewrite or redact the signature.

**Why It Happens:** Device Trust puts the HMAC in the standard `Authorization` header, which proxies, API gateways and logging tools often treat as a credential.

**Workarounds:**

* Make sure every hop forwards `Authorization` unchanged to the code that verifies it.
* Don't let application middleware try to parse it as a bearer token.

**How Hookdeck Can Help:** Hookdeck verifies the signature as the request arrives and records every header, `Authorization` included, so you can see exactly what Device Trust sent before any of your own infrastructure touched it.

### Secret rotation changes the identifier

**The Problem:** Rotating the secret breaks a WAF rule that checks the identifier, as well as signature verification.

**Why It Happens:** The secret and `X-Kolide-Webhook-Identifier` change together, and there's no documented overlap period.

**Workarounds:**

* Update the secret and any identifier rule in the same change.
* Or create a second endpoint with the new secret, switch over, then delete the old one.

**How Hookdeck Can Help:** If Device Trust points at a Hookdeck source, only the source needs the new secret, and your WAF can allow Hookdeck's traffic instead.

### Payloads are thin for some events

**The Problem:** An event tells you something happened without all the detail you need, and `title` can be `null`.

**Why It Happens:** Several events carry IDs and a URL rather than the full object.

**Workarounds:**

* Fetch the device, issue or auth log from the API in a worker.
* Fall back to the check name when an issue has no title.

**How Hookdeck Can Help:** Hookdeck transformations can reshape events for each destination, for example mapping `check.name` into a title field your ticketing system expects.

## Best practices

### Treat `Authorization` as data

Read it, verify it, and keep it out of logs, but never pass it to an authentication layer.

### Deduplicate on `id`

Every event has an `id`. Store it with a unique constraint as the [idempotency guide](/webhooks/guides/implement-webhook-idempotency) describes, especially for events that open tickets or page someone.

### Decouple alerts from receipt

Creating a ticket or posting to chat belongs after the response. [Asynchronous webhook processing](/webhooks/guides/why-implement-asynchronous-processing-webhooks) explains why.

### Use separate endpoints for separate consumers

A SIEM wants `audit_log.recorded` and `auth_logs.*`, while a helpdesk wants `issues.*` and `requests.*`. Separate endpoints keep each consumer's subscriptions and secret independent.

## Conclusion

1Password Device Trust webhooks report device trust, issue, sign-in and admin activity, signed with a hex HMAC-SHA256 of the body in the `Authorization` header. Verify the raw bytes, preserve that header through every proxy, deduplicate on `id`, and don't count on retries, because Device Trust doesn't document them.

[Hookdeck Event Gateway](https://hookdeck.com) gives Device Trust events durable storage and retries you can see.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle 1Password Device Trust (Kolide) webhooks reliably in minutes.