# 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=`. 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.