> ## Documentation Index
> Fetch the complete documentation index at: https://hilt.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive signed HTTP POSTs when findings are created or reviewed and when detection rules fire.

Webhooks push events to your endpoint as they happen, so you don't have to poll `GET /v1/findings`. Register a URL, pick the event types you want, and verify each delivery's signature.

***

## Registering a webhook

Webhooks are managed in the dashboard under **Settings → Integrations → Webhooks** (admin role required):

1. Click **Add webhook** and enter your endpoint URL (`https://` recommended; internal addresses are fine for on-prem receivers).
2. Choose the event types to receive.
3. Copy the **signing secret** (`whsec_...`) shown after registration — like API keys, it is displayed exactly once.

The same page shows each webhook's **delivery log**: one row per delivery with its attempt count, current status (`delivered`, `retrying`, `failed`, `pending`), the most recent attempt's HTTP status code and error detail, and its created / next-retry / completed timestamps.

## Event types

| Event | Fires when |
| - | - |
| `finding.created` | A new behavioral-anomaly finding is detected |
| `finding.reviewed` | A finding is accepted or denied — via the dashboard or the API |
| `detection_rule.matched` | One of your custom detection rules fires |

Detection-rule matches are delivered as `detection_rule.matched`, not `finding.created` — subscribe to both event types to cover everything that appears in [`GET /v1/findings`](https://hilt.ai/docs/api/findings).

Events are scoped like everything else: a webhook only receives events for hosts its registering user can see.

## Payload

Every delivery is a `POST` with a JSON body:

```json theme={null}
{
  "id": "evt_2f6a9c1d4b8e0a37",
  "type": "finding.created",
  "created_at": "2026-07-07T14:02:41Z",
  "data": {
    "finding_id": "web-prod-01-1000-1783058049000000000",
    "finding": { "...": "same shape as GET /v1/findings/{id}" }
  }
}
```

* `finding.reviewed` adds `status` (`accepted`/`denied`), `reviewed_by`, and `notes` to `data`.
* `detection_rule.matched` adds `rule` (`{id, name, label}`) to `data`.
* `data.finding_id` is always present; use it with [`GET /v1/findings/{id}`](https://hilt.ai/docs/api/findings#get-one-finding) if you need the full object later.

## Verifying signatures

Each delivery carries these headers:

| Header | Contents |
| - | - |
| `X-Hilt-Event-Id` | The event ID (also in the body) — dedup on this |
| `X-Hilt-Event-Type` | The event type |
| `X-Hilt-Timestamp` | Unix seconds when the delivery was attempted |
| `X-Hilt-Signature` | `sha256=` + hex HMAC-SHA256 over `"<timestamp>.<body>"` |

Recompute the HMAC with your signing secret over the timestamp, a literal `.`, and the **raw request body**, then compare in constant time:

```python theme={null}
import hashlib, hmac

def verify(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
```

```javascript theme={null}
const crypto = require("crypto");

function verify(secret, timestamp, rawBody, signature) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(signature), b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

Reject deliveries whose signature doesn't match, and consider rejecting timestamps older than a few minutes to guard against replays.

## Delivery and retries

* Respond with any `2xx` within 15 seconds to acknowledge a delivery. Redirects are not followed and count as failures.
* On failure, delivery retries with increasing delays — **1 minute, 5 minutes, 30 minutes, then 2 hours** — and is marked `failed` after 5 total attempts. Each delivery's attempt count and latest outcome are recorded in the delivery log.
* Delivery is at-least-once: rare duplicates are possible, so dedup on `X-Hilt-Event-Id` if your handler isn't idempotent.
* A newly registered webhook receives events from that moment on — the existing backlog of historical findings is not replayed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.