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

# Webhooks

> Receive approval-granted, approval-denied, and audit-export events from Inviolet.

Inviolet posts webhook events to your HTTP endpoint when something
worth knowing happens out-of-band — most commonly when a human
approver grants or denies a pending intent request. Every event is
HMAC-signed; verify the signature before trusting the payload.

## Configure a destination

App dashboard → **Settings → Webhooks → Add endpoint**.

* **URL** — your HTTPS endpoint
* **Signing secret** — copy this; you cannot view it again
* **Events** — pick `approval.granted`, `approval.denied`,
  `audit.export.completed`, etc.

## Payload shape

```json theme={"dark"}
{
  "event": "approval.granted",
  "organization_id": "org_2pX9...",
  "created_at": "2026-04-26T12:34:56Z",
  "data": {
    "approval_request_id": "ar_2pX9...",
    "user_id": "user_2pX9...",
    "intent_label": "pii_export",
    "data_elements": ["customer.email", "customer.phone"],
    "data_source_id": "salesforce_prod",
    "grant": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "grant_id": "it_2pX9...",
    "approved_by": "user_admin42",
    "expires_at": "2026-04-26T12:39:56Z"
  }
}
```

## Headers

| Header                 | Description                                   |
| ---------------------- | --------------------------------------------- |
| `inviolet-event`       | Event type, e.g. `approval.granted`           |
| `inviolet-delivery-id` | Unique delivery UUID — use for idempotency    |
| `inviolet-timestamp`   | Unix ms — reject if `> 5 min` skew            |
| `inviolet-signature`   | `sha256=<hex>` HMAC of `${timestamp}.${body}` |

## Verify with the Node SDK

The Node SDK ships a constant-time signature verifier:

```ts theme={"dark"}
import { InvioletWebhook } from "@inviolet/sdk"

const webhook = new InvioletWebhook({
  secret: process.env.INVIOLET_WEBHOOK_SECRET!,
})

// In your route handler (Next.js App Router shown):
export async function POST(req: Request) {
  try {
    const event = await webhook.constructEvent(req)
    if (event.event === "approval.granted") {
      // event.data.grant is the JWT now valid for downstream calls
    }
    return new Response("ok")
  } catch (err) {
    return new Response("invalid signature", { status: 401 })
  }
}
```

## Verify by hand

`signature = sha256=hex(HMAC-SHA256(secret, timestamp + "." + body))`.
Compare with constant-time equality. The SDK `verifyWebhookSignature`
function exposes this primitive directly if you need to verify
without constructing the full event object.

## Retries

Inviolet retries 4xx and 5xx responses with exponential backoff for
24 hours. After that, the event is moved to a dead-letter queue
visible in the dashboard.

## Read next

* **[Intent Events](/api-reference/intent-events)** — the API surface
  that mints the tokens these webhooks deliver
* **[Intentions](/api-reference/intentions)**
