Skip to content
NimbusNexus

Webhooks API

The REST API behind Webhooks: publish events, manage the endpoints that receive them, and inspect, redeliver and replay deliveries. Every operation below shows its request in the language picked at the top of the page. Where the SDK or the CLI has the operation, you see that call; otherwise you see a plain HTTP request.

Calls take an API key for Webhooks, created in your account console. Its scope decides what it can do: admin can do everything, publish can only publish, and read can only read. Writes that take a project apply to the workspace's default project unless you pass project_id.

Base URL
https://webhooks.nimbusnexus.net
Authentication
Authorization: Bearer <API key>
Version
Webhooks 0.11.33 · OpenAPI spec

Before the examples

The examples read your API key from WEBHOOKS_API_KEY and use a client set up like this.

export WEBHOOKS_API_KEY="<your-api-key>"
curl "https://webhooks.nimbusnexus.net/v1/auth/me" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"

Events

GET/v1/events

List Events

Parameters

NameInTypeDescription
event_typequerystring | null—
project_idquerystring | null—
limitqueryinteger—
offsetqueryinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/events" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/events

Publish Event

Parameters

NameInTypeDescription
Idempotency-Keyheaderstring | null—

Request body

FieldTypeDescription
event_type*string—
payload*object—
project_idstring | null—
sourcestring | null—

Responses

  • 201Successful Response
  • 401error.code = unauthorized
  • 402error.code = quota_exceeded
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/events" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "order.created",
    "payload": {
      "id": "ord_123",
      "total": 4200
    }
  }'
GET/v1/events/{event_id}

Get Event

A single event WITH its producer payload — the fan-out drill-down's 'why did this fire'.

Parameters

NameInTypeDescription
event_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/events/<event_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/events/{event_id}/deliveries

Event Deliveries

Parameters

NameInTypeDescription
event_id*pathstring—
limitqueryinteger—
offsetqueryinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/events/<event_id>/deliveries" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/events/{event_id}/replay

Replay Event

DASH-5: replay a specific past event to a CHOSEN endpoint — arm a NEW signed delivery of the event's stored payload to endpoint_id (even one not originally subscribed), reusing the standard delivery-creation/enqueue path. Admin-only (require_admin); workspace-scoped — the event AND the endpoint must belong to the caller's workspace (404 otherwise) and the endpoint must be enabled (409). Rate-limited on the SHARED redeliver bucket (API-4). Returns the created delivery id(s).

Parameters

NameInTypeDescription
event_id*pathstring—

Request body

FieldTypeDescription
endpoint_id*string—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/events/<event_id>/replay" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint_id": "<endpoint_id>"
  }'

Endpoints

GET/v1/endpoints

List Endpoints

Parameters

NameInTypeDescription
project_idquerystring | null—
limitqueryinteger—
offsetqueryinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/endpoints" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/endpoints

Create Endpoint

Request body

FieldTypeDescription
url*string—
subscriptionsarray of SubscriptionIn—
secretstring | null—
max_attemptsinteger | null—
retry_schedulearray of number | null—
project_idstring | null—
descriptionstring | null—
custom_headersobject | null—
delivery_timeout_msinteger | null—

Responses

  • 201Successful Response
  • 401error.code = unauthorized
  • 402error.code = quota_exceeded
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/endpoints" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks"
  }'
GET/v1/endpoints/{endpoint_id}

Get Endpoint

Parameters

NameInTypeDescription
endpoint_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
PATCH/v1/endpoints/{endpoint_id}

Update Endpoint

Parameters

NameInTypeDescription
endpoint_id*pathstring—

Request body

FieldTypeDescription
urlstring | null—
max_attemptsinteger | null—
retry_schedulearray of number | null—
status"enabled" | "disabled" | null—
descriptionstring | null—
custom_headersobject | null—
delivery_timeout_msinteger | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 402error.code = workspace_frozen
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X PATCH "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
DELETE/v1/endpoints/{endpoint_id}

Delete Endpoint

Parameters

NameInTypeDescription
endpoint_id*pathstring—

Responses

  • 204Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X DELETE "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/endpoints/{endpoint_id}/enable

Enable Endpoint

Re-enable an auto-disabled (or disabled) endpoint so it rejoins fan-out — the circuit breaker is one-way, so this is the explicit, authenticated operator recovery action.

Parameters

NameInTypeDescription
endpoint_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>/enable" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/endpoints/{endpoint_id}/rotate-secret

Rotate Secret

Parameters

NameInTypeDescription
endpoint_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>/rotate-secret" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/endpoints/{endpoint_id}/stats

Get Endpoint Stats

B4: per-endpoint success rate / delivered-failed-dead / latency p50-p95 / volume buckets over a bounded window (24h hourly | 7d daily), aggregated on-the-fly. Workspace-scoped (404).

Parameters

NameInTypeDescription
endpoint_id*pathstring—
windowquery"24h" | "7d"—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>/stats" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
PUT/v1/endpoints/{endpoint_id}/subscriptions

Replace Subscriptions

Parameters

NameInTypeDescription
endpoint_id*pathstring—

Request body

FieldTypeDescription
subscriptionsarray of SubscriptionIn—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X PUT "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>/subscriptions" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
POST/v1/endpoints/{endpoint_id}/test

Test Endpoint

Send a synthetic signed ping to the endpoint and return the immediate attempt result, so an operator can validate connectivity + signature without publishing a real event. Nothing is persisted; SSRF-guarded like a real delivery. The ping carries the endpoint's custom headers, as a real delivery does, so a subscriber that requires one (an Authorization its gateway checks) answers the ping as it answers deliveries. Their values never appear in the response.

More

NO SESSION IS OPEN DURING THE OUTBOUND CALL. The ping used to run inside the same workspace_session that loaded the endpoint, which parked a pooled connection idle-in-transaction for up to the subscriber's timeout (the per-endpoint clamp reaches 60 s). A handful of slow subscribers being pinged from the console was enough to drain the API pool for everyone else. So: a short session to authorise and load, closed; the POST; a second short session to write the audit row. Rate-limited on the redeliver bucket — like a replay, this is an outbound request to a customer-chosen host, made on demand.

Parameters

NameInTypeDescription
endpoint_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>/test" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/endpoints/{endpoint_id}/verify

Verify Endpoint

API-3 echo-challenge verification (onboarding gate): sign + POST a one-off webhook.verify carrying a random challenge token; the subscriber must return 2xx AND echo the token. On success the endpoint is marked verified (verified on EndpointOut). SSRF-guarded; nothing persisted beyond the verified flag. The challenge carries the endpoint's custom headers, as a real delivery does, so a subscriber that requires one can be verified; their values never appear in the response.

More

Structured like the test ping above, and for the same reason: no pooled connection is held across the outbound POST. The verified stamp is written in a SECOND session against a freshly loaded row, because the endpoint may have moved while the challenge was in flight.

Parameters

NameInTypeDescription
endpoint_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/endpoints/<endpoint_id>/verify" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"

Deliveries

GET/v1/deliveries

List Deliveries

Parameters

NameInTypeDescription
statusquerystring | null—
endpoint_idquerystring | null—
event_typequerystring | null—
project_idquerystring | null—
sincequerystring | null—
untilquerystring | null—
qquerystring | null—
limitqueryinteger—
offsetqueryinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/deliveries" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/deliveries/redeliver

Bulk Redeliver

Re-arm a filtered batch of settled deliveries (default the DLQ) — the recover-after-outage op, e.g. all dead deliveries for an endpoint once the subscriber is healthy again.

Request body

FieldTypeDescription
status"dead" | "failed"—
endpoint_idstring | null—
project_idstring | null—
limitinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/deliveries/redeliver" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
POST/v1/deliveries/replay

Replay Deliveries

FEAT-3: replay-by-time-range — re-arm settled deliveries created in [from, to] (the DLQ by default) for a fresh attempt, e.g. recover what dead-lettered during an outage window. Workspace-scoped (RLS). Enforces the API-4 blast-radius controls: the redeliver rate limit (429)

  • the per-call limit cap (<=10000, validated by the schema). Reuses the bulk-redeliver CAS, so a row a worker just claimed is not clobbered.

Request body

FieldTypeDescription
from*string—
to*string—
status"dead" | "failed"—
endpoint_idstring | null—
project_idstring | null—
limitinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/deliveries/replay" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "2026-10-19T12:00:00Z",
    "to": "2026-10-19T12:00:00Z"
  }'
GET/v1/deliveries/{delivery_id}

Get Delivery

Parameters

NameInTypeDescription
delivery_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/deliveries/<delivery_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/deliveries/{delivery_id}/attempts

Delivery Attempts

Parameters

NameInTypeDescription
delivery_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/deliveries/<delivery_id>/attempts" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/deliveries/{delivery_id}/redeliver

Redeliver

Parameters

NameInTypeDescription
delivery_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/deliveries/<delivery_id>/redeliver" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"

Event types

GET/v1/event-types

List Event Types

The catalog governing a project: its own entries PLUS the workspace-wide ones.

More

NULL rows are included, not excluded — they apply to every project, so they govern this one too, and hiding them would show a catalog that does not match what publishing actually accepts.

Parameters

NameInTypeDescription
project_idquerystring | null—
limitqueryinteger—
offsetqueryinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/event-types" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/event-types

Create Event Type

Register a type. project_id scopes it to one project (shadowing any shared entry of the same name); omitted registers it workspace-wide, as before.

Request body

FieldTypeDescription
name*string—
descriptionstring | null—
project_idstring | null—
payload_schemaobject | null—

Responses

  • 201Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/event-types" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Example"
  }'
GET/v1/event-types/policy

Get Event Type Policy

The policy IN FORCE — the project's own override when it has one, else the workspace's.

More

Reports the effective value rather than the workspace's raw setting, because the raw setting is not what publishing will do: a console scoped to a project that overrides it would otherwise display lenient while every publish into that project was being rejected as strict.

Parameters

NameInTypeDescription
project_idquerystring | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/event-types/policy" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
PUT/v1/event-types/policy

Set Event Type Policy

Set the policy for one project (?project_id=) or for the whole workspace.

More

A project-level set writes an OVERRIDE; it does not touch the workspace value, so projects that inherit keep inheriting and keep tracking later workspace changes.

Parameters

NameInTypeDescription
project_idquerystring | null—

Request body

FieldTypeDescription
policy*"strict" | "lenient"—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X PUT "https://webhooks.nimbusnexus.net/v1/event-types/policy" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "policy": "strict"
  }'
DELETE/v1/event-types/{name}

Delete Event Type

Delete the entry in exactly the named scope — the project's when project_id is given, the workspace-wide one when it is not. Deleting from a project leaves the shared entry alone.

Parameters

NameInTypeDescription
name*pathstring—
project_idquerystring | null—

Responses

  • 204Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X DELETE "https://webhooks.nimbusnexus.net/v1/event-types/<name>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"

Sources

POST/v1/ingest/{source_id}No API key

Ingest Event

PUBLIC inbound-webhook ingest — the only unauthenticated write surface. Auth is the provider's HMAC signature over the EXACT raw bytes, verified against the source's signing secret (current + rotation-overlap previous) — the industry contract for third-party webhooks; providers cannot send bearer tokens.

More

Response opacity: a 404 conflates unknown AND disabled sources (it never reveals whether an id ever existed), and every signature failure — malformed, non-ASCII, or wrong — returns one uniform 401 that never reveals WHY. (A 401 does confirm a known id is currently enabled, but ids are unguessable random tokens, so this is not an enumeration surface, and 401-on-bad- signature is the ecosystem-standard signal providers surface for a misconfigured secret.)

A verified request is published into the normal pipeline; the provider's event id becomes a namespaced idempotency key so provider retries replay instead of double-fanning-out.

Parameters

NameInTypeDescription
source_id*pathstring—

Responses

  • 202Successful Response
  • 401error.code = unauthorized
  • 402error.code = quota_exceeded
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/ingest/<source_id>"
GET/v1/sources

List Sources

A project's ingest sources. IngestSource.project_id is NOT NULL, so unlike the catalog and the operational subscriptions there is no "applies everywhere" row to include here.

Parameters

NameInTypeDescription
project_idquerystring | null—
limitqueryinteger—
offsetqueryinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/sources" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/sources

Create Source

Request body

FieldTypeDescription
provider*"stripe" | "github" | "rabbitmq" | "generic"—
namestring—
project_idstring | null—
event_type_source"provider" | "header" | "path"—
event_type_keystring—
secretstring | null—
event_type_prefixstring—
default_event_typestring—
capture_enabledboolean—
broker_identifierstring—
broker_hoststring—
broker_portinteger—
broker_vhoststring—
broker_userstring—
broker_exchangestring—
broker_topicstring—
broker_queues"transient" | "durable" | "quorum"—

Responses

  • 201Successful Response
  • 401error.code = unauthorized
  • 402error.code = not_entitled
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/sources" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "stripe"
  }'
GET/v1/sources/{source_id}

Get Source

Parameters

NameInTypeDescription
source_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/sources/<source_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
PATCH/v1/sources/{source_id}

Patch Source

Parameters

NameInTypeDescription
source_id*pathstring—

Request body

FieldTypeDescription
namestring | null—
status"enabled" | "disabled" | null—
event_type_source"provider" | "header" | "path" | null—
event_type_keystring | null—
event_type_prefixstring | null—
default_event_typestring | null—
capture_enabledboolean | null—
broker_identifierstring | null—
broker_hoststring | null—
broker_portinteger | null—
broker_vhoststring | null—
broker_userstring | null—
broker_exchangestring | null—
broker_topicstring | null—
broker_queues"transient" | "durable" | "quorum" | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 402error.code = not_entitled
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X PATCH "https://webhooks.nimbusnexus.net/v1/sources/<source_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
DELETE/v1/sources/{source_id}

Delete Source

Parameters

NameInTypeDescription
source_id*pathstring—

Responses

  • 204Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X DELETE "https://webhooks.nimbusnexus.net/v1/sources/<source_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/sources/{source_id}/rotate-secret

Rotate Source Secret

Parameters

NameInTypeDescription
source_id*pathstring—

Request body

JSON body required.

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/sources/<source_id>/rotate-secret" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Captured requests

GET/v1/captures

List Captures

Captured requests, newest activity first.

More

ONE ROW PER DISTINCT REQUEST — that is the dedup, and it is what makes this list readable. A provider retrying a failed delivery eight times produces one row with seen_count: 8, not eight rows burying everything else on the page.

ORDERED BY last_seen_at, NOT first_seen_at. A request that is still being retried is live news; sorting by when we first saw it would sink an actively-failing delivery below older, settled traffic — which is the opposite of what somebody opens this page to find.

NO BODIES HERE. A list of fifty captures is a list of fifty third-party payloads, and sending them all to render a table that shows none of them is both wasteful and a wider exposure than the page needs. The body is on the detail read, one deliberate click later.

SCOPED BY PROJECT like /v1/sources: ?project_id= narrows to the captures of that project's sources, and a confined caller's reach is applied whether or not they name one. Both are applied through the source (module docstring). ?source_id= and ?outcome= still narrow further, and a source_id outside the workspace — or, for a confined caller, outside their reach — is a 404 rather than an empty page, so the id cannot be used as an existence oracle.

Parameters

NameInTypeDescription
source_idquerystring | null—
outcomequerystring | null—
project_idquerystring | null—
limitqueryinteger—
offsetqueryinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/captures" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/captures/{capture_id}

Get Capture

One capture, with the bytes.

More

THE BODY IS BASE64, not a string, and this is not decoration. A capture is kept precisely because it may NOT be valid UTF-8 — a body that failed to parse is one of the outcomes this feature exists to explain, and a JSON string field cannot carry it without lying about what arrived. The console decodes for display and says so when it cannot.

Parameters

NameInTypeDescription
capture_id*pathstring—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/captures/<capture_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
DELETE/v1/captures/{capture_id}

Delete Capture

Drop one capture.

More

A DELETE ON A READ SCOPE, deliberately. This table holds third-party payloads a customer did not choose to receive, and the person who notices that something sensitive landed in it is whoever is looking at the page. Making them find an admin first is how the data stays there longer. Nothing else in the workspace depends on a capture, so the blast radius of a mistaken delete is one row of diagnostics.

Parameters

NameInTypeDescription
capture_id*pathstring—

Responses

  • 204Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X DELETE "https://webhooks.nimbusnexus.net/v1/captures/<capture_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"

Operational endpoints

GET/v1/operational-endpoints

List Operational Endpoints

The subscriptions receiving a project's operational events — those pinned to it PLUS the workspace-wide ones, which receive every project's. The secret is never re-returned.

Parameters

NameInTypeDescription
project_idquerystring | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/operational-endpoints" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
POST/v1/operational-endpoints

Create Operational Endpoint

Subscribe to webhookd's operational events (endpoint.disabled/recovered/degraded, message.attempt.exhausted), delivered as SIGNED webhooks to your URL. The URL must be https; the signing secret is returned ONCE (verify deliveries with the SDK, like any webhook).

Request body

FieldTypeDescription
url*string—
event_typesarray of string | null—
project_idstring | null—
secretstring | null—

Responses

  • 201Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X POST "https://webhooks.nimbusnexus.net/v1/operational-endpoints" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks"
  }'
DELETE/v1/operational-endpoints/{op_id}

Delete Operational Endpoint

Unsubscribe (remove) an operational-webhook endpoint.

Parameters

NameInTypeDescription
op_id*pathstring—

Responses

  • 204Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X DELETE "https://webhooks.nimbusnexus.net/v1/operational-endpoints/<op_id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"

Verification policy

GET/v1/verification-policy

Get Project Verification

Whether this project requires endpoint verification, and WHY.

More

Reports the effective value alongside both inputs, because "required" alone cannot be acted on: an admin seeing it on has no way to tell whether their own project set it or the workspace imposes it, and only one of those is theirs to change.

UNSCOPED, this answers for the WORKSPACE — the floor that applies to every project — and not for a project picked on the caller's behalf. It used to substitute the workspace's default project, which was wrong three ways at once: it reported one project's override as though it were the answer for all of them, it handed a project-confined caller the state of a project outside their reach, and on a workspace with no projects yet (the first-login window, before the first projection lands) the substitution raised and the route answered 500.

Parameters

NameInTypeDescription
project_idquerystring | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/verification-policy" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
PUT/v1/verification-policy

Set Project Verification

Set THIS project's own verification requirement.

More

Additive: the workspace value is a floor, so required: false clears the project's requirement but cannot lift one the workspace imposes — the response says what fan-out will actually do. To require verification everywhere, set it on the WORKSPACE (PATCH /v1/workspace); this route deliberately cannot reach that value, so a project-scoped admin can never widen its own blast radius to the whole workspace.

Parameters

NameInTypeDescription
project_idquerystring | null—

Request body

FieldTypeDescription
required*boolean—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X PUT "https://webhooks.nimbusnexus.net/v1/verification-policy" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "required": true
  }'

Projects

GET/v1/projects

List Projects

The projects webhookd knows about for this workspace — ids only.

More

NOT the console's project picker any more: a picker needs names, and the name belongs to Identity. The console lists projects from Identity and uses these ids to scope webhookd calls.

is_default is STATED, not implied by position. It used to be inferable only from the sort order, so a client that wanted the routing default had to know to read row zero — a contract carried in a comment, which any later ordering change would have broken silently. The flag is now derived from workspace.default_project_id, the one place that fact lives. Rows stay default-first so existing callers see no change.

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/projects" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
DELETE/v1/projects/{project_id}

Delete Project

Parameters

NameInTypeDescription
project_id*pathstring—

Responses

  • 204Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X DELETE "https://webhooks.nimbusnexus.net/v1/projects/<project-id>" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"

Workspace and plan

GET/v1/plan

Get Plan

B6: the workspace's plan ceilings + current usage, for the dashboard headroom view.

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/plan" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/stats

Get Stats

At-a-glance summary for the dashboard overview, for one project or the whole workspace.

More

project_id narrows every count in the response, deliveries_used included. That figure used to stay workspace-wide whatever was passed, which handed a project-confined credential the whole workspace's delivery volume on a route it was otherwise contained on, and showed a scoped console one project's endpoints beside the workspace's deliveries.

The QUOTA and the PLAN do stay workspace-wide, because they genuinely are workspace facts: the plan is bought once and its ceiling is shared, so a project's usage is a share of one ceiling rather than a ceiling of its own.

Parameters

NameInTypeDescription
project_idquerystring | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/stats" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/stats/backlog

Get Backlog

FEAT-5: delivery backlog (queued/sending/failed/pending + DLQ) plus rate-limit headroom (remaining publish tokens vs the configured rate/burst). Workspace-scoped at the app layer AND, on Postgres, by RLS; project_id narrows the backlog counts. The headroom does NOT narrow — the token bucket is keyed on the workspace, so every project draws on the same tokens. It is a non-consuming peek, so polling this never burns one.

Parameters

NameInTypeDescription
project_idquerystring | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/stats/backlog" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/stats/history

Get Stats History

FEAT-4: delivery time-series over a bounded window (24h hourly | 7d/30d daily) — per-bucket total / delivered / failed counts + success rate, aggregated on-the-fly from the raw deliveries table. Workspace-scoped (same workspace filter as the other stats endpoints + RLS on Postgres) and narrowed to project_id when given. Latency is omitted (the deliveries table carries no timing columns); see StatsHistoryOut.latency.

Parameters

NameInTypeDescription
windowquery"24h" | "7d" | "30d"—
project_idquerystring | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/stats/history" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/usage

Get Usage

Current-cycle usage vs the plan quota — for ONE project, or the whole workspace.

More

Scoped like every other read: naming a project reports that project's usage, omitting it reports the workspace's. A credential confined to specific projects never sees more than the projects it reaches, whichever form it uses.

The QUOTA stays the workspace's either way, and deliberately: it is enforced per workspace, so reporting a per-project share of it would invent a ceiling that nothing enforces. A project view answers "how much of the workspace's allowance did THIS project spend", which is the question someone scoped to a project is actually asking.

Parameters

NameInTypeDescription
project_idquerystring | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/usage" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
GET/v1/workspace

Get Current Workspace

The caller's own workspace — plan + settings. No name and no owner email: both are Identity's, and a copy here would only go stale (the email also made webhookd the sender of somebody's mail, which it is not).

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/workspace" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"
PATCH/v1/workspace

Patch Current Workspace

Partial-update the workspace's operational settings (admin scope) — today just the API-3 require-verify gate. Only the keys present in the body are applied.

Request body

FieldTypeDescription
require_endpoint_verificationboolean | null—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 402error.code = not_entitled
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X PATCH "https://webhooks.nimbusnexus.net/v1/workspace" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Audit log

GET/v1/audit

List Audit

SEC-3 control-plane audit trail (admin-only). Workspace-scoped at the app layer (WHERE workspace_id) AND by RLS; optional ?action= / ?target_type= / ?project_id= filters.

More

A project filter EXCLUDES rows with a NULL project — those are workspace-level actions (settings changed, or a workspace-wide key minted back when this service issued keys) and they belong to no project. This is the opposite of how NULL is treated on the catalog and operational-subscription filters, where it means "applies everywhere"; see the note on AuditLog.project_id.

Parameters

NameInTypeDescription
actionquerystring | null—
target_typequerystring | null—
project_idquerystring | null—
limitqueryinteger—
offsetqueryinteger—

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/audit" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"

Authentication

GET/v1/auth/me

Auth Me

Which principal is this credential? Answered from what require_token already verified.

More

NOT gated on the read scope: "which principal am I" must be answerable by ANY authenticated caller, including one that may not read workspace data. A credential that cannot discover what it is cannot be used correctly.

THAT NOW INCLUDES MACHINE CREDENTIALS. This route used to 403 every non-human caller, which contradicted the paragraph immediately above it: an api key could not find out what it was. The dashboard's api-key mode therefore had to GUESS its scope, and guessed optimistically — showing admin affordances the server then refused. Nothing answered here is a disclosure: every field is a property of the very credential the caller is already holding.

id is the PRINCIPAL's identity, and its shape follows the kind: Identity's user id for a human (not a webhookd row id — webhookd stores no users), and the credential label otherwise (key:<prefix>, a service JWT's sub, or bootstrap). The label is a DISPLAY string, so treat it as one — it tells the caller what it is; it is not a handle to look anything up by. role is the human tier and is empty for a machine credential, which has none.

email and created_at are null because they are not token claims, so webhookd genuinely never sees them; Identity's own /api/v1/account/me is the source for those. scope is the field the dashboard needs here to gate admin affordances proactively, and it is exactly the scope require_token enforced.

Responses

  • 200Successful Response
  • 401error.code = unauthorized
  • 403error.code = forbidden
  • 404error.code = not_found
  • 409error.code = conflict
  • 413error.code = payload_too_large
  • 422error.code = validation_error
  • 429error.code = rate_limited
  • 500error.code = internal_error

Example

curl -X GET "https://webhooks.nimbusnexus.net/v1/auth/me" \
  -H "Authorization: Bearer $WEBHOOKS_API_KEY"