Skip to content

Webhooks API ​

Manage webhook subscriptions — the signed events Console sends to your URL when something happens in your agents — and inspect their deliveries. Concepts, the event catalogue, signing and retries are in the Webhooks guide; this page is the route reference.

Two surfaces expose the same routes with the same rules; they differ only in how they authenticate:

SurfaceBase pathAuth
Client API/api/client/v1/webhooksAuthorization: Bearer cpeer_…
Dashboard/api/webhooksBrowser session

Everything below is shown for the client API.

Conventions ​

  • Endpoint and delivery objects are camelCase (projectId, disabledReason, failingSince, eventType, nextAttemptAt), like the other resource-management endpoints. (The agent endpoints you call from a handler — runs, sessions, events — are snake_case.)
  • Errors are { "error": "<message>", "code": "<code>" }.
  • A token acts in its own project only, and manages that project's endpoints when its owner is a project admin or a tenant owner / admin. It never manages a tenant-wide endpoint or another project's, whoever owns it. Anyone else gets 403 forbidden; an endpoint outside your reach is 404 not_found.
  • A tenant can have at most 50 endpoints.

Endpoint map ​

Endpoint
GET /webhooks/eventsThe event catalogue
GET /webhooks, POST /webhooksList; create (returns secret once)
GET /webhooks/{id}, PATCH /webhooks/{id}, DELETE /webhooks/{id}Read; edit url, description, events, status; delete
POST /webhooks/{id}/rotate-secretA new secret (returned once)
POST /webhooks/{id}/testSend a webhook.test event now and return the delivery
GET /webhooks/{id}/deliveriesDelivery attempts, newest first
POST /webhooks/deliveries/{deliveryId}/redeliverSend a delivery again

Objects ​

Endpoint ​

json
{
  "id": "6720b4c3d2e1f0a9b8c7d6e5",
  "url": "https://example.com/hooks/cognipeer",
  "description": "ops pager",
  "events": ["action.required", "agent_run.*"],
  "projectId": "6a1b2c3d4e5f60718293a4b5",
  "status": "active",
  "disabledReason": null,
  "failingSince": null,
  "createdBy": "6a1b2c3d4e5f60718293a4b6",
  "createdAt": "2026-10-11T09:00:00.000Z",
  "updatedAt": "2026-10-11T09:00:00.000Z"
}
Field
urlAn http(s) URL that resolves to a public address.
eventsExact types and/or prefix.* patterns; * for everything.
projectIdThe project the endpoint is limited to; null = every project of the tenant.
statusactive or disabled.
disabledReasonmanual, redirect, blocked_address or sustained_failure; null while active.
failingSinceISO timestamp the endpoint started failing continuously; cleared by any 2xx.

The secret is never part of this object — it appears only in the responses to create and rotate-secret, as secret (whsec_…).

Delivery ​

json
{
  "id": "6721c5d4e3f2a1b0c9d8e7f6",
  "endpointId": "6720b4c3d2e1f0a9b8c7d6e5",
  "eventId": "evt_0123456789abcdef01234567",
  "eventType": "session.idled",
  "status": "delivered",
  "attempts": 1,
  "nextAttemptAt": null,
  "lastStatusCode": 200,
  "lastError": null,
  "lastDurationMs": 84,
  "createdAt": "2026-10-11T09:14:03.200Z",
  "deliveredAt": "2026-10-11T09:14:03.290Z",
  "payload": {
    "id": "evt_0123456789abcdef01234567",
    "type": "session.idled",
    "createdAt": "2026-10-11T09:14:03.120Z",
    "projectId": "6a1b2c3d4e5f60718293a4b5",
    "data": { "conversationId": "6f1c0a2b9d3e4f5a6b7c8d9e", "agentKey": "support-bot", "stopReason": "end_turn" }
  }
}

status is pending (owed — a first attempt or a scheduled retry; nextAttemptAt says when), delivered (a 2xx), or failed (it will not be tried again). lastError is Endpoint answered HTTP 500, Timed out after 10000 ms, Gave up after 8 attempts, and so on.

Event catalogue ​

http
GET /api/client/v1/webhooks/events
json
{ "events": [ { "type": "session.idled", "group": "session" }, { "type": "action.required", "group": "action" }, "…" ] }

Groups: session, action, budget, feedback, agent_run, schedule. The types are session.idled, session.work_done, action.required, budget.reached, feedback.recorded, agent_run.succeeded, agent_run.failed, agent_run.canceled, agent_run.requires_action, schedule.fired, schedule.failed. (webhook.test is only ever sent by the test route.) Each event's data is documented in the guide.

Create ​

http
POST /api/client/v1/webhooks
Content-Type: application/json
Body fieldType
urlstring, requiredAt most 2048 characters; http or https; must resolve to a public address (checked now, and again on every delivery).
eventsstring[], requiredNon-empty. Each a known type, a prefix.* that matches at least one known type, or *. Duplicates are removed.
descriptionstringAt most 500 characters.
projectIdstring | nullOmitted = the token's project — the only value a client token may use. (The dashboard also takes null or "*" — every project — for tenant owners/admins, or another project's id.)
bash
curl -X POST "$CONSOLE_URL/api/client/v1/webhooks" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/hooks/cognipeer", "events": ["action.required", "agent_run.*"], "description": "ops pager" }'
typescript
const hook = await client.webhooks.create({
  url: 'https://example.com/hooks/cognipeer',
  events: ['action.required', 'agent_run.*'],
  description: 'ops pager',
});
console.log(hook.id, hook.secret); // the secret is shown only now

201: { "endpoint": { …endpoint, "secret": "whsec_…" } }. Errors: 400 (url is required, url is not a valid URL, url must use http or https, url must resolve to a public address, events must be a non-empty array of event types, Unknown event type "x", description must be at most 500 characters), 403 forbidden, 409 limit_reached.

List, read, update, delete ​

http
GET    /api/client/v1/webhooks             → { "endpoints": [ … ] }
GET    /api/client/v1/webhooks/{id}        → { "endpoint": { … } }
PATCH  /api/client/v1/webhooks/{id}        → { "endpoint": { … } }
DELETE /api/client/v1/webhooks/{id}        → { "deleted": true }

PATCH accepts any of url, description (an empty string clears it), events and status ("active" or "disabled"); omitted fields are untouched. Switching to active re-checks the address and clears disabledReason and failingSince; switching to disabled is recorded as manual and marks the endpoint's still-queued deliveries failed. DELETE also deletes the delivery history.

bash
# Re-enable after fixing the receiver
curl -X PATCH "$CONSOLE_URL/api/client/v1/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
  -d '{ "status": "active" }'

SDK: client.webhooks.list() / get(id) / update(id, body) / delete(id). Errors: 400 invalid_request, 403 forbidden, 404 not_found.

Rotate the secret ​

http
POST /api/client/v1/webhooks/{id}/rotate-secret

200: { "endpoint": { …endpoint, "secret": "whsec_…" } }. The old secret stops working at once; the new one is shown only here. SDK: client.webhooks.rotateSecret(id).

Send a test event ​

http
POST /api/client/v1/webhooks/{id}/test

Sends one webhook.test event now, waits for the receiver and returns the delivery with what it answered. One attempt, no retries; the answer does not count towards the endpoint's health; it works on a disabled endpoint too.

200: { "delivery": { …, "status": "delivered" | "failed", "lastStatusCode": 200, "lastError": null } } — a failing receiver is a failed delivery in a 200 answer, not an error. SDK: client.webhooks.test(id).

Deliveries ​

http
GET /api/client/v1/webhooks/{id}/deliveries?status=failed&limit=50&skip=0
Query
statuspending, delivered or failed.
limitDefault 50, at most 200.
skipOffset, for paging.

200: { "deliveries": [ … ], "hasMore": true }, newest first. SDK: client.webhooks.deliveries(id, { status, limit, skip }).

Redeliver ​

http
POST /api/client/v1/webhooks/deliveries/{deliveryId}/redeliver

Puts the delivery back in the queue — the same event id, to the endpoint's current URL and secret (for example after fixing the receiver). 202: { "delivery": { …, "status": "pending" } }; it is sent asynchronously. Errors: 404 not_found, 409 endpoint_disabled (enable the endpoint first), 409 delivery_pending (it is already waiting to be sent). SDK: client.webhooks.redeliver(deliveryId).

Error codes ​

StatusCode
400invalid_requestA field was rejected; the message says which.
403forbiddenThe caller may not manage webhooks (or a webhook for every project / another project).
404not_foundNo such endpoint or delivery within the caller's reach.
409limit_reachedThe tenant already has 50 endpoints.
409endpoint_disabledRedeliver on a disabled endpoint.
409delivery_pendingRedeliver on a delivery that is still waiting.
500internal_errorThe test delivery could not be recorded.

Consoles with the Authorization engine

On a Console that includes the Authorization (IAM) engine, webhooks is an administration service: webhooks.read allows GET calls (with the endpoint URL's userinfo and query string removed) and every call that changes something needs webhooks.admin; a denial is 403 and 503 authorization_unavailable means the engine could not decide (retry; it is never an allow).

SDK mapping ​

Endpoint@cognipeer/console-sdk (3.1.0+)
GET /webhooks/eventsclient.webhooks.eventTypes()
POST /webhooksclient.webhooks.create()
GET /webhooks, GET /webhooks/{id}client.webhooks.list(), client.webhooks.get()
PATCH, DELETE /webhooks/{id}client.webhooks.update(), client.webhooks.delete()
POST …/rotate-secret, …/testclient.webhooks.rotateSecret(), client.webhooks.test()
GET …/deliveries, POST …/redeliverclient.webhooks.deliveries(), client.webhooks.redeliver()
Signature checkverifyWebhook() from @cognipeer/console-sdk/webhooks

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub