# Configure Webhooks

Webhooks let AgentIdP push real-time events to your application when agents, credentials, or
tokens change state. This guide covers creating subscriptions, the available event types,
delivery guarantees, and how to inspect delivery history.

---

## Prerequisites

- A running AgentIdP instance
- A valid Bearer token with `organization_id` in its claims
- A publicly reachable HTTPS endpoint to receive events (for local development, use a tool
  like [ngrok](https://ngrok.com))

---

## Available event types

| Event type | Triggered when |
|-----------|----------------|
| `agent.created` | A new agent is registered |
| `agent.updated` | An agent's metadata is updated |
| `agent.suspended` | An agent's status changes to `suspended` |
| `agent.reactivated` | An agent's status changes from `suspended` to `active` |
| `agent.decommissioned` | An agent is decommissioned |
| `credential.generated` | New credentials are created for an agent |
| `credential.rotated` | A credential's secret is rotated |
| `credential.revoked` | A credential is revoked |
| `token.issued` | An access token is issued |
| `token.revoked` | An access token is revoked |

---

## Create a subscription

`POST /api/v1/webhooks`

```bash
curl -s -X POST http://localhost:3000/api/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "prod-agent-events",
    "url": "https://my-app.example.com/hooks/sentryagent",
    "events": ["agent.created", "agent.decommissioned", "token.issued"]
  }' | jq .
```

Response (`201 Created`):

```json
{
  "id": "wh-1a2b3c4d-e5f6-7890-abcd-ef1234567890",
  "organization_id": "org-0a1b2c3d-e4f5-6789-abcd-ef0123456789",
  "name": "prod-agent-events",
  "url": "https://my-app.example.com/hooks/sentryagent",
  "events": ["agent.created", "agent.decommissioned", "token.issued"],
  "active": true,
  "signingSecret": "whsec_a1b2c3d4e5f6789...",
  "failure_count": 0,
  "created_at": "2026-04-04T09:00:00.000Z",
  "updated_at": "2026-04-04T09:00:00.000Z"
}
```

> **Save the `signingSecret` now.** It is shown once. Use it to verify the HMAC-SHA256
> signature on incoming webhook requests. See "Verifying delivery signatures" below.

```bash
export WEBHOOK_ID="wh-1a2b3c4d-e5f6-7890-abcd-ef1234567890"
export SIGNING_SECRET="whsec_a1b2c3d4e5f6789..."
```

---

## Webhook payload format

Every delivery sends a POST to your URL with `Content-Type: application/json` and this body:

```json
{
  "id": "evt-uuid-here",
  "event": "agent.created",
  "timestamp": "2026-04-04T09:00:00.000Z",
  "organization_id": "org-0a1b2c3d-e4f5-6789-abcd-ef0123456789",
  "data": {
    "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "email": "screener-001@talent.ai",
    "agentType": "screener"
  }
}
```

The `data` object contains event-specific fields. For `agent.*` events it includes agent
metadata. For `credential.*` events it includes `credentialId` and `agentId`. For `token.*`
events it includes `agentId` and `scope`.

---

## Verifying delivery signatures

AgentIdP signs every delivery with HMAC-SHA256 using your `signingSecret`. The signature is
in the `X-SentryAgent-Signature` header as `sha256=<hex-digest>`.

Verify it in Node.js:

```javascript
const crypto = require('crypto');

function verifySignature(rawBody, signingSecret, signatureHeader) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', signingSecret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
```

Always verify the signature before processing the event. Reject requests with invalid signatures
with `401 Unauthorized`.

---

## Delivery guarantees and retry policy

- AgentIdP delivers each event **at least once** — your endpoint may receive duplicates
- Use the `id` field to deduplicate events
- Delivery is attempted immediately; on failure, retries use exponential backoff
- After repeated failures, the delivery moves to `dead_letter` status
- Subscriptions with high `failure_count` may be automatically disabled

Delivery statuses: `pending` → `delivered` (success) or `failed` (attempt failed) → `dead_letter`
(all retries exhausted)

---

## List subscriptions

```bash
curl -s "http://localhost:3000/api/v1/webhooks" \
  -H "Authorization: Bearer $TOKEN" | jq .
```

---

## Pause or resume a subscription

To pause (disable) a subscription without deleting it:

```bash
curl -s -X PATCH "http://localhost:3000/api/v1/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "active": false }' | jq .
```

To resume:

```bash
curl -s -X PATCH "http://localhost:3000/api/v1/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "active": true }' | jq .
```

---

## Inspect delivery history

`GET /api/v1/webhooks/{id}/deliveries`

```bash
curl -s "http://localhost:3000/api/v1/webhooks/$WEBHOOK_ID/deliveries?limit=20&offset=0" \
  -H "Authorization: Bearer $TOKEN" | jq .
```

Response:

```json
{
  "deliveries": [
    {
      "id": "del-uuid",
      "subscription_id": "wh-uuid",
      "event_type": "agent.created",
      "payload": { ... },
      "status": "delivered",
      "http_status_code": 200,
      "attempt_count": 1,
      "next_retry_at": null,
      "delivered_at": "2026-04-04T09:00:01.000Z",
      "created_at": "2026-04-04T09:00:00.000Z",
      "updated_at": "2026-04-04T09:00:01.000Z"
    }
  ],
  "total": 47,
  "limit": 20,
  "offset": 0
}
```

Use `offset` to paginate through delivery history. Increase `limit` to retrieve more records
per page (the server default is 20).

---

## Delete a subscription

```bash
curl -s -X DELETE "http://localhost:3000/api/v1/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -o /dev/null -w "%{http_code}\n"
```

Expected response: `204`. This permanently deletes the subscription and all its delivery records.
