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

# Webhooks

> Push HMAC-signed HTTP POST payloads to external URLs on auth events via createWebhookModule. Subscribe each endpoint to specific event types or all events.

## What webhooks do

Webhooks push signed HTTP POST requests to a URL you control whenever a KavachOS auth event occurs. Use them to sync user records, trigger onboarding flows, alert on suspicious logins, or feed events into your analytics pipeline.

## Setup

```typescript theme={"system"}
import { createKavach, createWebhookModule } from 'kavachos';

const kavach = await createKavach({
  database: { provider: 'sqlite', url: 'kavach.db' },
  plugins: [
    createWebhookModule({
      secret: process.env.KAVACH_WEBHOOK_SECRET,
      endpoints: [
        {
          url: 'https://myapp.com/webhooks/kavach',
          events: ['user.created', 'auth.login', 'agent.created'],
        },
      ],
    }),
  ],
});
```

<Warning>
  Store the webhook secret in an environment variable, not in source code. KavachOS uses it to sign every request with HMAC-SHA256.
</Warning>

## Subscribing to events

Each endpoint subscribes to one or more event types. Use `'*'` to receive all events.

```typescript theme={"system"}
createWebhookModule({
  secret: process.env.KAVACH_WEBHOOK_SECRET,
  endpoints: [
    {
      url: 'https://myapp.com/webhooks/all',
      events: ['*'],
    },
    {
      url: 'https://myapp.com/webhooks/agents',
      events: ['agent.created', 'agent.revoked'],
    },
  ],
});
```

## Event reference

| Event                | Fired when                                |
| -------------------- | ----------------------------------------- |
| `user.created`       | A new human user registers                |
| `user.deleted`       | A user account is deleted                 |
| `auth.login`         | A successful login occurs                 |
| `auth.logout`        | A session is terminated                   |
| `auth.failed`        | A login attempt fails                     |
| `agent.created`      | A new agent identity is created           |
| `agent.revoked`      | An agent is revoked                       |
| `delegation.granted` | An agent receives a delegation grant      |
| `delegation.revoked` | A delegation grant is removed             |
| `approval.requested` | A human approval request is opened        |
| `approval.resolved`  | An approval request is approved or denied |

## Request headers

Every webhook delivery includes these headers:

| Header               | Value                                        |
| -------------------- | -------------------------------------------- |
| `X-Kavach-Signature` | `sha256=<hmac>`. HMAC-SHA256 of the raw body |
| `X-Kavach-Event`     | Event type, e.g. `user.created`              |
| `X-Kavach-Delivery`  | Unique UUID for this delivery attempt        |
| `X-Kavach-Timestamp` | Unix timestamp (seconds) of delivery         |

## Verifying signatures

Always verify the signature before trusting the payload.

<Tabs>
  <Tab title="Node">
    ```typescript theme={"system"}
    import { createHmac, timingSafeEqual } from 'node:crypto';

    function verifyWebhook(rawBody: Buffer, signature: string, secret: string): boolean {
      const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
      return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
    }

    // Express handler
    app.post('/webhooks/kavach', express.raw({ type: 'application/json' }), (req, res) => {
      const sig = req.headers['x-kavach-signature'] as string;
      if (!verifyWebhook(req.body, sig, process.env.KAVACH_WEBHOOK_SECRET!)) {
        return res.status(401).send('Invalid signature');
      }
      const event = JSON.parse(req.body.toString());
      // handle event...
      res.sendStatus(200);
    });
    ```
  </Tab>

  <Tab title="Edge (Web Crypto)">
    ```typescript theme={"system"}
    async function verifyWebhook(rawBody: string, signature: string, secret: string): Promise<boolean> {
      const key = await crypto.subtle.importKey(
        'raw',
        new TextEncoder().encode(secret),
        { name: 'HMAC', hash: 'SHA-256' },
        false,
        ['sign'],
      );
      const mac = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(rawBody));
      const expected = 'sha256=' + Array.from(new Uint8Array(mac)).map(b => b.toString(16).padStart(2, '0')).join('');
      return expected === signature;
    }
    ```
  </Tab>
</Tabs>

## Retry behavior

If your endpoint returns a non-2xx status or times out, KavachOS retries the delivery three times with exponential backoff:

| Attempt | Delay      |
| ------- | ---------- |
| 1       | 30 seconds |
| 2       | 5 minutes  |
| 3       | 30 minutes |

After three failures the delivery is marked `failed` and no further retries occur.

## Testing a webhook URL

Use the `kavach.webhooks.test()` method to send a synthetic `ping` event to any registered endpoint:

```typescript theme={"system"}
await kavach.webhooks.test('https://myapp.com/webhooks/kavach');
```

The test delivery sends `{ event: 'ping', timestamp: '...' }` and respects the same signing and retry logic as real events.

## Next steps

<CardGroup cols={2}>
  <Card title="Lifecycle hooks" href="/hooks">
    Run async callbacks on auth events inside the SDK process.
  </Card>

  <Card title="Audit log" href="/audit">
    Query the full record of every authorization decision.
  </Card>
</CardGroup>
