docs: commit all Phase 6 documentation updates and OpenSpec archives
- devops docs: 8 files updated for Phase 6 state; field-trial.md added (946-line runbook) - developer docs: api-reference (50+ endpoints), quick-start, 5 existing guides updated, 5 new guides added - engineering docs: all 12 files updated (services, architecture, SDK guide, testing, overview) - OpenSpec archives: phase-7-devops-field-trial, developer-docs-phase6-update, engineering-docs-phase6-update - VALIDATOR.md + scripts/start-validator.sh: V&V Architect tooling added - .gitignore: exclude session artifacts, build artifacts, and agent workspaces Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
219
docs/developers/guides/configure-webhooks.md
Normal file
219
docs/developers/guides/configure-webhooks.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user