All findings from the inaugural LeadValidator audit resolved and confirmed. Release gate: PASS. VV_ISSUE_002 (BLOCKER): 15 OpenAPI specs verified present covering all 20 route groups (46 endpoints documented in docs/openapi/) VV_ISSUE_003 (MAJOR): Remove any types from src/db/pool.ts — replaced pool.query shim with unknown[] + Object.defineProperty, zero any types, eslint-disable suppressions removed VV_ISSUE_004 (MAJOR): Remove raw Pool from ScaffoldController and HealthDetailedController — injected AgentRepository/CredentialRepository and DbProbe interface respectively; added CredentialRepository.findActiveClientId() VV_ISSUE_005 (MAJOR): Add unit tests for 5 untested services — ComplianceStatusStore, EventPublisher, MarketplaceService, OIDCTrustPolicyService, UsageService VV_ISSUE_006 (MAJOR): Add integration tests for 7 missing route groups — analytics, billing, tiers, webhooks, marketplace, oidc-trust-policies, oidc-token-exchange VV_ISSUE_001 (MINOR): Create missing design.md and tasks.md in 4 OpenSpec archives — all archives now complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
677 lines
22 KiB
YAML
677 lines
22 KiB
YAML
openapi: "3.0.3"
|
|
|
|
info:
|
|
title: SentryAgent.ai — Webhooks & Event Subscriptions
|
|
version: 1.0.0
|
|
description: |
|
|
Webhook subscription management and delivery history endpoints for the
|
|
SentryAgent.ai AgentIdP platform.
|
|
|
|
Webhooks deliver real-time event notifications to registered HTTP endpoints
|
|
when significant platform events occur (agent lifecycle changes, credential
|
|
operations, token events).
|
|
|
|
**All endpoints require a valid Bearer JWT** with appropriate scope:
|
|
- `webhooks:read` — required for GET operations
|
|
- `webhooks:write` — required for POST, PATCH, DELETE operations
|
|
|
|
**Delivery mechanism:**
|
|
- Events are delivered via `POST` to the registered `url`
|
|
- Each delivery carries a signed JSON envelope (`X-SentryAgent-Signature` header)
|
|
- Failed deliveries are retried with exponential backoff (up to 10 attempts)
|
|
- After 10 failures the subscription is marked `dead_letter`
|
|
|
|
**Supported event types:**
|
|
`agent.created`, `agent.updated`, `agent.suspended`, `agent.reactivated`,
|
|
`agent.decommissioned`, `credential.generated`, `credential.rotated`,
|
|
`credential.revoked`, `token.issued`, `token.revoked`
|
|
|
|
servers:
|
|
- url: http://localhost:3000/api/v1
|
|
description: Local development server
|
|
- url: https://api.sentryagent.ai/v1
|
|
description: Production server
|
|
|
|
tags:
|
|
- name: Webhook Subscriptions
|
|
description: Create and manage webhook endpoint subscriptions
|
|
- name: Webhook Deliveries
|
|
description: Query delivery history and attempt records
|
|
|
|
components:
|
|
securitySchemes:
|
|
BearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: |
|
|
JWT access token obtained via `POST /token`.
|
|
Include as `Authorization: Bearer <token>`.
|
|
|
|
schemas:
|
|
WebhookEventType:
|
|
type: string
|
|
enum:
|
|
- agent.created
|
|
- agent.updated
|
|
- agent.suspended
|
|
- agent.reactivated
|
|
- agent.decommissioned
|
|
- credential.generated
|
|
- credential.rotated
|
|
- credential.revoked
|
|
- token.issued
|
|
- token.revoked
|
|
description: Platform event type that can be subscribed to.
|
|
example: agent.created
|
|
|
|
WebhookDeliveryStatus:
|
|
type: string
|
|
enum:
|
|
- pending
|
|
- delivered
|
|
- failed
|
|
- dead_letter
|
|
description: |
|
|
Current status of a delivery attempt.
|
|
- `pending` — queued for delivery or awaiting retry
|
|
- `delivered` — successfully delivered (HTTP 2xx from target)
|
|
- `failed` — delivery failed; retry scheduled
|
|
- `dead_letter` — all retries exhausted; no further attempts
|
|
example: delivered
|
|
|
|
WebhookSubscription:
|
|
type: object
|
|
description: A registered webhook subscription (signing secret never included in responses).
|
|
required:
|
|
- id
|
|
- organization_id
|
|
- name
|
|
- url
|
|
- events
|
|
- active
|
|
- failure_count
|
|
- created_at
|
|
- updated_at
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
description: Immutable system-assigned UUID for this subscription.
|
|
readOnly: true
|
|
example: "wh-abcd-1234-5678-ef01"
|
|
organization_id:
|
|
type: string
|
|
format: uuid
|
|
description: Organization that owns this subscription.
|
|
readOnly: true
|
|
example: "org-1234-5678-abcd-ef01"
|
|
name:
|
|
type: string
|
|
description: Human-readable label for this subscription.
|
|
example: "Agent lifecycle events"
|
|
url:
|
|
type: string
|
|
format: uri
|
|
description: HTTPS endpoint that receives webhook payloads.
|
|
example: "https://my-app.example.com/webhooks/sentryagent"
|
|
events:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/WebhookEventType'
|
|
description: List of event types this subscription receives.
|
|
minItems: 1
|
|
example:
|
|
- agent.created
|
|
- agent.decommissioned
|
|
active:
|
|
type: boolean
|
|
description: Whether the subscription is currently active. Set to false to pause delivery.
|
|
example: true
|
|
failure_count:
|
|
type: integer
|
|
description: Number of consecutive delivery failures since last success.
|
|
minimum: 0
|
|
readOnly: true
|
|
example: 0
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
readOnly: true
|
|
example: "2026-03-01T08:00:00.000Z"
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
readOnly: true
|
|
example: "2026-03-28T11:30:00.000Z"
|
|
|
|
CreateWebhookRequest:
|
|
type: object
|
|
description: Request body for creating a new webhook subscription.
|
|
required:
|
|
- name
|
|
- url
|
|
- events
|
|
properties:
|
|
name:
|
|
type: string
|
|
description: Human-readable label for this subscription.
|
|
minLength: 1
|
|
maxLength: 256
|
|
example: "Agent lifecycle events"
|
|
url:
|
|
type: string
|
|
format: uri
|
|
description: HTTPS endpoint URL. Must be HTTPS in production.
|
|
example: "https://my-app.example.com/webhooks/sentryagent"
|
|
events:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/WebhookEventType'
|
|
minItems: 1
|
|
description: Event types to subscribe to.
|
|
example:
|
|
- agent.created
|
|
- agent.decommissioned
|
|
|
|
UpdateWebhookRequest:
|
|
type: object
|
|
description: |
|
|
Request body for partially updating a webhook subscription.
|
|
All fields are optional; only provided fields are updated.
|
|
minProperties: 1
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 256
|
|
example: "Agent events (updated)"
|
|
url:
|
|
type: string
|
|
format: uri
|
|
example: "https://my-app.example.com/webhooks/v2"
|
|
events:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/WebhookEventType'
|
|
minItems: 1
|
|
example:
|
|
- agent.created
|
|
- agent.updated
|
|
- token.issued
|
|
active:
|
|
type: boolean
|
|
description: Set to false to pause delivery without deleting the subscription.
|
|
example: true
|
|
|
|
WebhookDelivery:
|
|
type: object
|
|
description: A single webhook delivery attempt record.
|
|
required:
|
|
- id
|
|
- subscription_id
|
|
- event_type
|
|
- payload
|
|
- status
|
|
- attempt_count
|
|
- created_at
|
|
- updated_at
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
readOnly: true
|
|
example: "del-abcd-1234-5678-ef01"
|
|
subscription_id:
|
|
type: string
|
|
format: uuid
|
|
example: "wh-abcd-1234-5678-ef01"
|
|
event_type:
|
|
$ref: '#/components/schemas/WebhookEventType'
|
|
payload:
|
|
type: object
|
|
description: The JSON payload that was sent (or attempted) to the target URL.
|
|
additionalProperties: true
|
|
example:
|
|
id: "evt-1234-5678"
|
|
event: "agent.created"
|
|
timestamp: "2026-04-07T09:00:00.000Z"
|
|
organization_id: "org-1234-5678-abcd-ef01"
|
|
data:
|
|
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
status:
|
|
$ref: '#/components/schemas/WebhookDeliveryStatus'
|
|
http_status_code:
|
|
type: integer
|
|
nullable: true
|
|
description: HTTP status code returned by the target endpoint (null if connection failed).
|
|
example: 200
|
|
attempt_count:
|
|
type: integer
|
|
description: Number of delivery attempts made so far.
|
|
minimum: 1
|
|
example: 1
|
|
next_retry_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Scheduled time for the next retry attempt. Null if delivered or dead-lettered.
|
|
example: null
|
|
delivered_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Timestamp when the delivery was successfully confirmed.
|
|
example: "2026-04-07T09:00:05.000Z"
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
readOnly: true
|
|
example: "2026-04-07T09:00:00.000Z"
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
readOnly: true
|
|
example: "2026-04-07T09:00:05.000Z"
|
|
|
|
PaginatedDeliveriesResponse:
|
|
type: object
|
|
description: Paginated delivery history response.
|
|
required:
|
|
- deliveries
|
|
- total
|
|
- limit
|
|
- offset
|
|
properties:
|
|
deliveries:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/WebhookDelivery'
|
|
total:
|
|
type: integer
|
|
example: 87
|
|
limit:
|
|
type: integer
|
|
example: 20
|
|
offset:
|
|
type: integer
|
|
example: 0
|
|
|
|
ErrorResponse:
|
|
type: object
|
|
description: Standard error response envelope.
|
|
required:
|
|
- code
|
|
- message
|
|
properties:
|
|
code:
|
|
type: string
|
|
example: "WEBHOOK_NOT_FOUND"
|
|
message:
|
|
type: string
|
|
example: "Webhook subscription with the specified ID was not found."
|
|
details:
|
|
type: object
|
|
additionalProperties: true
|
|
|
|
responses:
|
|
Unauthorized:
|
|
description: Missing or invalid Bearer token.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
code: "UNAUTHORIZED"
|
|
message: "A valid Bearer token is required to access this resource."
|
|
|
|
Forbidden:
|
|
description: Valid token but insufficient scope.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
code: "FORBIDDEN"
|
|
message: "You do not have permission to perform this action."
|
|
|
|
NotFound:
|
|
description: Webhook subscription not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
code: "WEBHOOK_NOT_FOUND"
|
|
message: "Webhook subscription with the specified ID was not found."
|
|
|
|
InternalServerError:
|
|
description: Unexpected server error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
code: "INTERNAL_SERVER_ERROR"
|
|
message: "An unexpected error occurred. Please try again later."
|
|
|
|
security:
|
|
- BearerAuth: []
|
|
|
|
paths:
|
|
/webhooks:
|
|
post:
|
|
operationId: createWebhookSubscription
|
|
tags:
|
|
- Webhook Subscriptions
|
|
summary: Create a webhook subscription
|
|
description: |
|
|
Creates a new webhook subscription for the authenticated organization.
|
|
A signing secret is generated automatically and returned once in the response
|
|
under a `signingSecret` field. **Store this secret securely — it cannot be retrieved again.**
|
|
|
|
Requires `webhooks:write` scope.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateWebhookRequest'
|
|
example:
|
|
name: "Agent lifecycle events"
|
|
url: "https://my-app.example.com/webhooks/sentryagent"
|
|
events:
|
|
- agent.created
|
|
- agent.decommissioned
|
|
responses:
|
|
'201':
|
|
description: Webhook subscription created successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: '#/components/schemas/WebhookSubscription'
|
|
- type: object
|
|
properties:
|
|
signingSecret:
|
|
type: string
|
|
description: |
|
|
HMAC signing secret for verifying webhook payloads.
|
|
Returned only once at creation time.
|
|
example: "whsec_abcdef1234567890abcdef1234567890"
|
|
example:
|
|
id: "wh-abcd-1234-5678-ef01"
|
|
organization_id: "org-1234-5678-abcd-ef01"
|
|
name: "Agent lifecycle events"
|
|
url: "https://my-app.example.com/webhooks/sentryagent"
|
|
events:
|
|
- agent.created
|
|
- agent.decommissioned
|
|
active: true
|
|
failure_count: 0
|
|
created_at: "2026-04-07T09:00:00.000Z"
|
|
updated_at: "2026-04-07T09:00:00.000Z"
|
|
signingSecret: "whsec_abcdef1234567890abcdef1234567890"
|
|
'400':
|
|
description: Validation error in request body.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
code: "VALIDATION_ERROR"
|
|
message: "Request validation failed."
|
|
details:
|
|
field: "url"
|
|
reason: "Must be a valid HTTPS URL."
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
get:
|
|
operationId: listWebhookSubscriptions
|
|
tags:
|
|
- Webhook Subscriptions
|
|
summary: List webhook subscriptions
|
|
description: |
|
|
Returns all webhook subscriptions for the authenticated organization.
|
|
Requires `webhooks:read` scope.
|
|
responses:
|
|
'200':
|
|
description: List of webhook subscriptions returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/WebhookSubscription'
|
|
example:
|
|
- id: "wh-abcd-1234-5678-ef01"
|
|
organization_id: "org-1234-5678-abcd-ef01"
|
|
name: "Agent lifecycle events"
|
|
url: "https://my-app.example.com/webhooks/sentryagent"
|
|
events:
|
|
- agent.created
|
|
- agent.decommissioned
|
|
active: true
|
|
failure_count: 0
|
|
created_at: "2026-03-01T08:00:00.000Z"
|
|
updated_at: "2026-03-01T08:00:00.000Z"
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/webhooks/{id}:
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
description: UUID of the webhook subscription.
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
example: "wh-abcd-1234-5678-ef01"
|
|
|
|
get:
|
|
operationId: getWebhookSubscription
|
|
tags:
|
|
- Webhook Subscriptions
|
|
summary: Get a webhook subscription by ID
|
|
description: |
|
|
Returns a single webhook subscription record.
|
|
Requires `webhooks:read` scope.
|
|
responses:
|
|
'200':
|
|
description: Webhook subscription returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WebhookSubscription'
|
|
example:
|
|
id: "wh-abcd-1234-5678-ef01"
|
|
organization_id: "org-1234-5678-abcd-ef01"
|
|
name: "Agent lifecycle events"
|
|
url: "https://my-app.example.com/webhooks/sentryagent"
|
|
events:
|
|
- agent.created
|
|
- agent.decommissioned
|
|
active: true
|
|
failure_count: 0
|
|
created_at: "2026-03-01T08:00:00.000Z"
|
|
updated_at: "2026-03-28T11:30:00.000Z"
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
patch:
|
|
operationId: updateWebhookSubscription
|
|
tags:
|
|
- Webhook Subscriptions
|
|
summary: Update a webhook subscription
|
|
description: |
|
|
Partially updates a webhook subscription. Only provided fields are updated.
|
|
Set `active: false` to pause delivery without deleting the subscription.
|
|
Requires `webhooks:write` scope.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateWebhookRequest'
|
|
example:
|
|
active: false
|
|
responses:
|
|
'200':
|
|
description: Webhook subscription updated successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WebhookSubscription'
|
|
example:
|
|
id: "wh-abcd-1234-5678-ef01"
|
|
organization_id: "org-1234-5678-abcd-ef01"
|
|
name: "Agent lifecycle events"
|
|
url: "https://my-app.example.com/webhooks/sentryagent"
|
|
events:
|
|
- agent.created
|
|
- agent.decommissioned
|
|
active: false
|
|
failure_count: 0
|
|
created_at: "2026-03-01T08:00:00.000Z"
|
|
updated_at: "2026-04-07T09:00:00.000Z"
|
|
'400':
|
|
description: Validation error in request body.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
code: "VALIDATION_ERROR"
|
|
message: "Request validation failed."
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
delete:
|
|
operationId: deleteWebhookSubscription
|
|
tags:
|
|
- Webhook Subscriptions
|
|
summary: Delete a webhook subscription
|
|
description: |
|
|
Permanently deletes a webhook subscription and stops all future deliveries.
|
|
Any pending deliveries in the queue are cancelled.
|
|
Requires `webhooks:write` scope.
|
|
responses:
|
|
'204':
|
|
description: Webhook subscription deleted successfully. No response body.
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/webhooks/{id}/deliveries:
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
description: UUID of the webhook subscription.
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
example: "wh-abcd-1234-5678-ef01"
|
|
|
|
get:
|
|
operationId: listWebhookDeliveries
|
|
tags:
|
|
- Webhook Deliveries
|
|
summary: List delivery history for a subscription
|
|
description: |
|
|
Returns the delivery history for a webhook subscription,
|
|
ordered by `created_at` descending (most recent first).
|
|
|
|
Use this endpoint to diagnose delivery failures and inspect
|
|
payload content for historical events.
|
|
|
|
Requires `webhooks:read` scope.
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 20
|
|
example: 20
|
|
- name: offset
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 0
|
|
default: 0
|
|
example: 0
|
|
- name: status
|
|
in: query
|
|
required: false
|
|
schema:
|
|
$ref: '#/components/schemas/WebhookDeliveryStatus'
|
|
description: Filter deliveries by status.
|
|
responses:
|
|
'200':
|
|
description: Delivery history returned successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/PaginatedDeliveriesResponse'
|
|
example:
|
|
deliveries:
|
|
- id: "del-abcd-1234-5678-ef01"
|
|
subscription_id: "wh-abcd-1234-5678-ef01"
|
|
event_type: "agent.created"
|
|
payload:
|
|
id: "evt-1234-5678"
|
|
event: "agent.created"
|
|
timestamp: "2026-04-07T09:00:00.000Z"
|
|
organization_id: "org-1234-5678-abcd-ef01"
|
|
data:
|
|
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
status: "delivered"
|
|
http_status_code: 200
|
|
attempt_count: 1
|
|
next_retry_at: null
|
|
delivered_at: "2026-04-07T09:00:05.000Z"
|
|
created_at: "2026-04-07T09:00:00.000Z"
|
|
updated_at: "2026-04-07T09:00:05.000Z"
|
|
total: 87
|
|
limit: 20
|
|
offset: 0
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|