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

# Webhooks

> Subscribe to Guardhouse events, validate signatures, and handle deliveries.

Webhooks let Guardhouse push notifications to your systems when events of interest occur —
for example, when an incident report is submitted or approved. You register one or more
**subscribers** (each owns a HMAC secret) and attach **subscriptions** (event/URI pairs)
to them.

This section requires an API key with `KeyType=webhook`.

## Event catalog

Subscriptions are identified by an `event` and an `eventType`. Today the supported pairs
are:

| `eventType`      | `event`   | Fires when…                                 |
| ---------------- | --------- | ------------------------------------------- |
| `incidentreport` | `submit`  | An incident report is submitted by a guard. |
| `incidentreport` | `approve` | An incident report is approved.             |
| `incidentreport` | `reject`  | An incident report is rejected.             |
| `timesheet`      | `submit`  | A timesheet is submitted.                   |
| `timesheet`      | `approve` | A timesheet is approved.                    |
| `timesheet`      | `reject`  | A timesheet is rejected.                    |

<Note>
  The catalog will grow. Unknown `event` / `eventType` combinations sent to
  `POST /api/webhook/subscriptions` return `400 Bad Request`.
</Note>

## Registration flow

### 1. Create a subscriber

```bash theme={null}
curl -X POST https://gateway-api.guardhousehq.com/api/webhook/subscribers \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

The response includes the `secretToken` you'll use for signature validation. **Store it
securely — it is shown only on creation and on rotation.**

```json theme={null}
{
  "status": { "code": 0, "errorMessage": "" },
  "data": {
    "id": 17,
    "companyId": 42,
    "secretToken": "whsec_4f8a…",
    "createdDateTime": "2026-05-04T15:00:00Z",
    "modifiedDateTime": null
  },
  "errors": []
}
```

### 2. Subscribe to an event

```bash theme={null}
curl -X POST https://gateway-api.guardhousehq.com/api/webhook/subscriptions \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriberId": 17,
    "event": "approve",
    "eventType": "incidentreport",
    "uri": "https://your-app.example.com/webhooks/guardhouse"
  }'
```

The `uri` must be reachable over HTTPS and respond `2xx` within a few seconds.

### 3. Receive notifications

Guardhouse will POST a JSON payload to your `uri` whenever the subscribed event fires.
Each request includes a `Webhooks-Signature` header containing an HMAC-SHA256 of the raw
request body, keyed by your subscriber's `secretToken`.

## Validating signatures

Compute the HMAC of the raw request body using your `secretToken` and compare against the
header value with a constant-time comparison.

```javascript theme={null}
import crypto from "node:crypto";

function isValidSignature(rawBody, headerSig, secretToken) {
  const expected = crypto
    .createHmac("sha256", secretToken)
    .update(rawBody)
    .digest("hex");
  // Use timingSafeEqual to avoid timing oracles.
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(headerSig, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

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

def is_valid_signature(raw_body: bytes, header_sig: str, secret_token: str) -> bool:
    expected = hmac.new(
        secret_token.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, header_sig)
```

If the signature does not match, respond with `403` and discard the message.

## Delivery and retries

* A delivery is considered successful when your endpoint responds `2xx` within the
  request timeout.
* Failed deliveries (`Failed`) are retried with backoff. After several failures the
  notification status flips to `MaxRetriesReached` and is no longer retried — fix the
  endpoint and contact support to replay missed events if needed.
* Notifications are delivered at-least-once. Use the event's identifying ID
  (`incidentReportId`, `timesheetId`) to deduplicate.

## Managing subscribers and subscriptions

| Operation                             | Endpoint                                                    |
| ------------------------------------- | ----------------------------------------------------------- |
| List a subscriber's subs              | `GET /api/webhook/subscribers/{subscriberId}/subscriptions` |
| Activate / deactivate / rotate secret | `PUT /api/webhook/subscribers/{id}`                         |
| Update a subscription URI / state     | `PUT /api/webhook/subscriptions/{id}`                       |

To rotate a subscriber's `secretToken`, send `{ "rotateSecretToken": true }` to the
subscriber update endpoint. The response will contain the new token; the previous token
will no longer validate signatures.
