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

# Webhooks

> Inbound AvatarCzar confidence updates (HMAC-SHA256 verified) and outbound webhook destinations for BossMode events.

# Webhooks

BossMode webhooks go two directions. **Inbound** events come from trusted partners — the primary example is AvatarCzar sending confidence updates when a persona's read on the market changes. **Outbound** events go from BossMode to whatever URL you configure — Slack, a warehouse, your own service — when directives execute, packs install, revenue is recorded, or the guardian kill engages.

Both directions are HMAC-SHA256 signed, timestamp-guarded to reject replays older than 5 minutes, and verified in constant time. Inbound endpoints resolve the sending customer from the payload's identifier (not from the body's `customerId` field) to block cross-tenant forgery. Outbound deliveries are at-least-once with exponential backoff — design handlers to be idempotent.

## Inbound webhooks — AvatarCzar confidence updates

BossMode accepts inbound confidence update events from AvatarCzar at:

```
POST /api/pro/avatarczar/induction
```

### Signature verification

Every inbound request from AvatarCzar is signed with HMAC-SHA256. The implementation is in `lib/avatarczar-inbound.ts`.

The signature is delivered in the `x-avatarczar-signature` request header in the format:

```
x-avatarczar-signature: sha256=<hex-digest>
```

To verify:

```typescript theme={null}
import { verifyAvatarCzarInboundSignature } from '@/lib/avatarczar-inbound';

const valid = verifyAvatarCzarInboundSignature({
  secret: process.env.AVATARCZAR_WEBHOOK_SECRET!,
  rawBody: rawBodyString,  // the raw, un-parsed request body string
  signature: request.headers.get('x-avatarczar-signature'),
});

if (!valid) {
  return new Response('Unauthorized', { status: 401 });
}
```

Verification uses a constant-time comparison (`crypto.timingSafeEqual`) to prevent timing attacks.

### Payload shape

The inbound payload is validated against the `avatarCzarConfidenceUpdateSchema` from `lib/avatarczar-inbound.ts`:

| Field                   | Type           | Description                                                  |
| ----------------------- | -------------- | ------------------------------------------------------------ |
| `workspaceId`           | `string`       | The BossMode workspace receiving the update                  |
| `customerEntitlementId` | `string`       | The entitlement being updated                                |
| `avatarczarTargetId`    | `string`       | The AvatarCzar target that generated the update              |
| `avatarczarEventId`     | `string`       | Idempotency key — deduplicate on this field                  |
| `metric`                | `enum`         | The confidence metric being updated (see `ALLOWED_METRICS`)  |
| `delta`                 | `number`       | Signed delta applied to the metric                           |
| `newConfidence`         | `number` (0–1) | The new confidence value after the update                    |
| `priorConfidence`       | `number` (0–1) | Optional — the prior value before this update                |
| `sourceBossmodeRef`     | `object`       | Optional — `{ table, id }` linking back to a BossMode record |
| `firedAt`               | `number`       | Unix timestamp (ms) when AvatarCzar fired the event          |

### Rate limit

`/api/pro/avatarczar/induction` — 60 requests per 60 seconds.

## Outbound webhooks — operator destinations

Configure outbound webhook destinations to receive BossMode events at your own endpoints.

### Configuration endpoint

```
POST /api/pro/avatarczar/webhooks
```

Rate limit: 5 requests per 60 seconds.

### Registering a destination

```bash theme={null}
curl -sS -X POST https://bossmode.ing/api/pro/avatarczar/webhooks \
  -H "x-bossmode-token: $BOSSMODE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Notify Slack when a directive completes",
    "url": "https://hooks.slack.com/services/T.../B.../..."
  }'
```

URLs are SSRF-validated before the config saves — private IPs, `localhost`, `169.254.0.0/16` metadata hosts, and non-HTTPS schemes are rejected.

### Event types

| Event                | When                                    |
| -------------------- | --------------------------------------- |
| `directive.created`  | A new boss directive is persisted       |
| `directive.executed` | A directive completes (success or fail) |
| `pack.installed`     | A pack fanout completes                 |
| `pack.revoked`       | A pack license is refunded or disputed  |
| `revenue.recorded`   | A new revenue event is logged           |
| `approval.requested` | An operator approval is queued          |
| `kill.activated`     | Guardian kill is engaged                |

### Request format

```http theme={null}
POST https://your-endpoint.example.com/bossmode-webhook
X-BossMode-Signature: t=1712345678,v1=<hmac-sha256-hex>
X-BossMode-Event: directive.executed
X-BossMode-RequestId: req_...
Content-Type: application/json

{
  "event": "directive.executed",
  "timestamp": 1712345678123,
  "workspaceId": "...",
  "payload": { ... }
}
```

### Verifying

```ts theme={null}
import crypto from 'node:crypto';

function verifyBossModeSignature(rawBody: string, header: string, secret: string): boolean {
  const [tPart, v1Part] = header.split(',');
  const timestamp = Number.parseInt(tPart.split('=')[1], 10);
  const signature = v1Part.split('=')[1];

  // Reject events older than 5 minutes
  if (Math.abs(Date.now() - timestamp * 1000) > 5 * 60 * 1000) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'));
}
```

### Retry policy

* At-least-once delivery. Design handlers to be idempotent.
* Up to 5 attempts with exponential backoff (1s → 10s → 60s → 10m → 1h).
* Response `2xx` = delivered. Any other status = retry. Response `410 Gone` = permanent failure; retries stop.

### Security

Outbound webhooks from BossMode are signed with HMAC-SHA256, using the same signing approach as AvatarCzar inbound events. Verify the `X-BossMode-Signature` header on your receiving endpoint.
