Skip to main content

Webhook Config

The Webhook resource configures where Adjudon delivers events. The delivery shape, signature scheme, retry schedule, and per-event payloads are documented at Webhooks Overview, Signature Verification, and Webhook Events Catalog. This page is the CRUD surface. Tested against API version v1. All endpoints are gated by the webhooks feature; mutating endpoints additionally require the admin or owner role.

The Webhook object

FieldTypeRequiredDescription
_idstringyesMongoDB ObjectId
organizationIdstringyesOwning org; chains never mix across tenants
workspaceIdstringnoWorkspace scope; null makes the webhook org-wide
urlstringyesDelivery URL; must be https://; SSRF-blocked addresses rejected
eventsstring[]yesOne or more event names; * for wildcard. See Events Catalog
typeenumnocustom (default), slack, teams
descriptionstringnoOperator-readable label, max 100 characters
isActivebooleannoDefault true; an inactive webhook does not receive deliveries
deliveryCountnumbernoTotal delivery attempts (read-only)
deliverySuccessCountnumbernoSuccessful 2xx deliveries (read-only)
lastTriggeredstring (ISO 8601)noMost recent delivery timestamp (read-only)
createdAt / updatedAtstring (ISO 8601)noStandard timestamps

The secret field exists on the schema but is never returned by any endpoint after creation. The raw value is shown once on the POST response — store it in your secret manager at that moment or rotate (delete-and-recreate) to mint a new one.

Endpoints

GET     /api/v1/webhooks
POST /api/v1/webhooks
PATCH /api/v1/webhooks/:id
DELETE /api/v1/webhooks/:id
POST /api/v1/webhooks/:id/test

GET is available to any authenticated org member; the four mutating endpoints require the admin or owner role.

List webhooks

GET /api/v1/webhooks
Query parameterDefaultDescription
limit(none)Cap server-side to 200; 1-200 range
searchCase-insensitive substring on url and description (max 100 chars)

Sort order: most-recently-created first. The x-workspace-id header scopes the response to one workspace; absent it returns every org-wide and workspace-tagged webhook.

curl
curl https://api.adjudon.com/api/v1/webhooks \
-H "Authorization: Bearer $ADJUDON_API_KEY"

Response (200 OK):

{
"success": true,
"data": [/* Webhook[] (without `secret`) */],
"count": 7
}

Errors: 401, 403 (UPGRADE_REQUIRED for plan-gate), 500 INTERNAL_ERROR.

Create a webhook

POST /api/v1/webhooks
Body fieldRequiredDescription
urlyeshttps:// URL; http://, localhost, RFC 1918, ::1, and 169.254.169.254 are all rejected
eventsyesNon-empty array; values must match the subscription enum or *
descriptionno≤100 characters
typenocustom, slack, or teams; defaults to custom
isActivenoDefaults to true
curl
curl -X POST https://api.adjudon.com/api/v1/webhooks \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/adjudon",
"events": ["trace.created", "alert.triggered"],
"description": "Production alerting",
"type": "custom"
}'
Python
import os, requests
r = requests.post(
"https://api.adjudon.com/api/v1/webhooks",
headers={"Authorization": f"Bearer {os.environ['ADJUDON_API_KEY']}"},
json={
"url": "https://hooks.example.com/adjudon",
"events": ["trace.created", "alert.triggered"],
},
)
secret = r.json()["data"]["signingSecret"] # save this — shown once

Response (201 Created):

{
"success": true,
"data": {
"_id": "65b1f2c4d3e0a1b2c3d4e5f6",
"organizationId":"65b1...",
"url": "https://hooks.example.com/adjudon",
"events": ["trace.created", "alert.triggered"],
"isActive": true,
"type": "custom",
"createdAt": "2026-05-06T10:14:22.317Z",
"signingSecret": "1f8a2c7b9d4e0a1b2c3d4e5f6789...."
},
"message": "Webhook created. Store the signing secret — it will not be shown again."
}

The signingSecret is the raw HMAC secret. Store it now. It will not appear in any subsequent GET response. If lost, delete the webhook and create a new one.

Errors: 400 VALIDATION_ERROR (missing url or empty events), 400 DUPLICATE (same URL + events combination), 400 SSRF_BLOCKED (via the URL validator at create time), 401, 403, 500.

Update a webhook

PATCH /api/v1/webhooks/:id

Partial update. Any of url, events, description, type, isActive may be supplied; omitted fields stay unchanged. The secret is not updateable on this path — rotate via delete-and-recreate.

curl — pause delivery
curl -X PATCH https://api.adjudon.com/api/v1/webhooks/65b1f2c4 \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "isActive": false }'

Errors: 400 VALIDATION_ERROR, 401 UNAUTHORIZED (different org), 404 NOT_FOUND, 500.

Test a webhook

POST /api/v1/webhooks/:id/test

Fires a synthetic event to the configured URL. The body shape is the same as a real delivery, with the event name webhook.test:

Test event body
{
"event": "webhook.test",
"timestamp": "2026-05-06T10:14:22.317Z",
"data": { "message": "This is a test webhook from ADJUDON" }
}
curl
curl -X POST https://api.adjudon.com/api/v1/webhooks/65b1f2c4/test \
-H "Authorization: Bearer $ADJUDON_API_KEY"

If the configured endpoint is unreachable or returns non-2xx, the test endpoint returns HTTP 200 with code: WEBHOOK_TEST_FAILED and the response detail. The non-failure status code is intentional: it surfaces the failure shape without triggering retry-cascade behaviour at the caller level.

Errors: 200 WEBHOOK_TEST_FAILED (delivery failure surfaced softly), 400 SSRF_BLOCKED (URL has since become unreachable on SSRF grounds), 401 UNAUTHORIZED, 404 NOT_FOUND, 500.

Delete a webhook

DELETE /api/v1/webhooks/:id

Hard-deletes the webhook record and stops further deliveries. The AuditLog entry on webhook.delete remains by Cardinal Rule 5 (operations chain is append-only).

curl
curl -X DELETE https://api.adjudon.com/api/v1/webhooks/65b1f2c4 \
-H "Authorization: Bearer $ADJUDON_API_KEY"

Errors: 401, 404 NOT_FOUND, 500.

Common gotchas

  • Secret shown once. The signingSecret field appears only on the POST create response. There is no recovery endpoint; lose it and you rotate via delete-and-recreate.
  • https:// only. Plain HTTP is rejected at validation. Private addresses (localhost, 127.x, 10.x, 192.168.x, 172.16-31.x, 0.0.0.0, ::1, AWS metadata 169.254.169.254) are blocked at create AND at delivery time (DNS-rebinding guard).
  • DUPLICATE on the create. A 400 DUPLICATE means the same (url, events[]) combination already exists. Use PATCH to re-enable an isActive: false webhook instead of recreating.
  • Test endpoint returns 200 on delivery failure. Branch on the response code (WEBHOOK_TEST_FAILED), not on the HTTP status, when distinguishing test success from a downstream delivery failure.

See also