Webhooks API

Read the outbound delivery log and attempts, manage endpoint and receiver configs, and redeliver a delivery.

The webhooks API backs the console's Webhooks view. It exposes the outbound delivery log (with per-attempt history), lets you redeliver a delivery, and provides full CRUD for the inbound receiver and outbound endpoint configs. Signing secrets are shown once when a config is created or rotated and are never returned by any read.

The config-write routes (POST/PATCH/DELETE on endpoints and receivers, and the redeliver route) require a secret API key. Durablex Cloud seals every signing secret at rest for you.

Method + pathPurpose
GET /webhook-deliveriesA page of outbound deliveries, newest first, plus an X-Next-Cursor header.
GET /webhook-deliveries/{id}One delivery with its full attempts log.
POST /webhook-deliveries/{id}/redeliverRe-queue a delivery for an immediate fresh attempt.
GET /webhook-endpointsThe outbound subscription configs (no secrets).
GET /webhook-endpoints/statsPer-endpoint delivery health over a window.
POST /webhook-endpointsCreate an outbound endpoint; returns the signing secret once.
PATCH /webhook-endpoints/{id}Edit an endpoint; optionally rotate its secret.
DELETE /webhook-endpoints/{id}Delete an endpoint.
GET /webhook-receiversThe inbound receiver configs (no secrets).
POST /webhook-receiversCreate an inbound receiver; returns the signing secret once.
PATCH /webhook-receivers/{id}Edit a receiver; optionally rotate its secret.
DELETE /webhook-receivers/{id}Delete a receiver.

See the webhooks guide for what produces these rows. Endpoints and receivers can also be managed from the console's Webhooks view; both paths write the same rows.

Listing deliveries

GET /webhook-deliveries accepts these query parameters:

ParamMeaningDefault
statusOne delivery status: pending, delivering, succeeded, failed, exhausted, dead.all
appRestrict to deliveries for one app's runs.all apps
limitPage size, 1-1000.100
cursorOpaque keyset cursor from a previous page's X-Next-Cursor.none

Paging is keyset over (createdAt, id) - the same model as GET /runs: the response carries up to limit deliveries plus an X-Next-Cursor header when more exist; the header is absent on the last page.

curl "$DURABLEX_ENGINE_URL/webhook-deliveries?app=shop&status=exhausted&limit=20"
import { createClient } from "@durablex/sdk/client";

const dx = createClient({ engineUrl: process.env.DURABLEX_ENGINE_URL! });
const page = await dx.webhooks.deliveries.list({ app: "shop", status: "exhausted", limit: 20 });
page.deliveries;  // WebhookDelivery[]
page.nextCursor;  // pass back as { cursor } for the next page

Each delivery has:

FieldMeaning
idDelivery id.
appThe app whose run produced the delivery.
endpointIdThe subscribed endpoint, or absent for a ctx.webhook.send.
urlThe destination the engine POSTs to.
eventKindrun.succeeded, run.failed, run.cancelled, step.failed, or custom.
sourceRunIdThe run whose lifecycle produced it (absent for a custom send).
payloadThe body the engine sends.
statuspending, delivering, succeeded, failed (awaiting retry), exhausted (retries spent), or dead (non-retryable response).
attemptCount / maxAttemptsAttempts made / allowed.
lastStatusCodeThe latest attempt's HTTP status (absent until a code is recorded).
nextAttemptAtWhen the sweeper next retries (while failed).
createdAt / updatedAtRFC3339 timestamps.

One delivery and its attempts

GET /webhook-deliveries/{id} returns the delivery above plus an attempts array - the append-only log of every POST the sweeper made, which is the per-attempt detail the console's delivery inspector shows. A wrong-namespace id reads back as 404.

{
  "id": "9f2b...", "app": "shop", "url": "https://hooks.example/sink",
  "eventKind": "run.failed", "status": "exhausted", "attemptCount": 5, "maxAttempts": 5,
  "attempts": [
    { "id": "a1", "attempt": 1, "outcome": "http_error", "statusCode": 500, "responseSnippet": "boom", "durationMs": 42, "createdAt": "2026-06-25T10:00:00Z" },
    { "id": "a2", "attempt": 2, "outcome": "timeout", "durationMs": 10000, "createdAt": "2026-06-25T10:00:11Z" }
  ]
}
Attempt fieldMeaning
attempt1-based attempt number.
outcomesucceeded, http_error, timeout, connection_error, or skipped.
statusCodeThe HTTP status, when the partner responded.
responseSnippetA bounded prefix of the response body, for debugging.
errorThe transport error, when there was no response.
durationMsHow long the attempt took.
curl "$DURABLEX_ENGINE_URL/webhook-deliveries/<id>"
const detail = await dx.webhooks.deliveries.get("<id>");
detail.attempts; // WebhookDeliveryAttempt[]

Redelivering a delivery

POST /webhook-deliveries/{id}/redeliver re-queues a delivery for an immediate fresh attempt and returns 204. It keeps the existing attempt log and grants a new retry budget, so the sweeper signs and POSTs it again. Use it to re-send a delivery that exhausted its retries, dead-lettered on a non-retryable response, or already succeeded (a manual re-send).

A delivery that is currently in flight (delivering) cannot be redelivered - the call returns 409 so a manual redeliver never races the sweeper. A missing or wrong-namespace id returns 404.

curl -X POST "$DURABLEX_ENGINE_URL/webhook-deliveries/<id>/redeliver"

Managing endpoints and receivers

GET /webhook-endpoints and GET /webhook-receivers return the outbound subscriptions and inbound receivers for the namespace. Both omit the signing secret entirely - it is sealed at rest and never leaves the engine in a read.

const endpoints = await dx.webhooks.endpoints.list(); // WebhookEndpoint[] (name?, url, scheme, eventKinds, enabled)
const receivers = await dx.webhooks.receivers.list(); // WebhookReceiver[] (name?, slug, eventName, scheme, enabled)

Creating

POST /webhook-endpoints creates an outbound subscription; POST /webhook-receivers creates an inbound receiver. The response includes the freshly generated secret once - store it now; it is never returned again. Supplying your own secret adopts it instead of generating one.

# outbound endpoint: deliver run lifecycle events to a URL
curl -X POST "$DURABLEX_ENGINE_URL/webhook-endpoints" \
  -H 'authorization: Bearer <secret-key>' -H 'content-type: application/json' \
  -d '{ "name": "Acme prod", "url": "https://hooks.example/sink", "eventKinds": ["run.failed", "run.succeeded"] }'

# inbound receiver: map a verified POST /webhooks/{slug} onto a Durablex event
curl -X POST "$DURABLEX_ENGINE_URL/webhook-receivers" \
  -H 'authorization: Bearer <secret-key>' -H 'content-type: application/json' \
  -d '{ "name": "Stripe", "eventName": "stripe.charge" }'
Endpoint bodyMeaning
nameOptional label; the console falls back to the URL when absent.
appOptional - restrict deliveries to one app; absent means all apps.
urlRequired destination.
eventKindsOne or more of run.succeeded, run.failed, run.cancelled, step.failed.
secretOptional - adopt a known secret instead of generating one.
Receiver bodyMeaning
nameOptional label; the console falls back to the slug when absent.
appOptional - the app the produced event belongs to.
eventNameRequired - the Durablex event a verified post is mapped to.
targetAppOptional - target a specific app for the produced event.
slugOptional - the public URL segment; generated when absent and immutable after create.
secretOptional - adopt a known signing secret instead of generating one.

A caller-supplied receiver slug that is already in use returns 409 (the slug is the global inbound routing key).

Editing, rotating, deleting

PATCH accepts a partial body - omitted fields are left unchanged. Set rotateSecret: true to issue a new signing secret; the response then carries the new secret once (it is absent on an edit that did not rotate). DELETE removes the config and returns 204. A wrong-namespace id returns 404.

# disable an endpoint and rotate its secret
curl -X PATCH "$DURABLEX_ENGINE_URL/webhook-endpoints/<id>" \
  -H 'authorization: Bearer <secret-key>' -H 'content-type: application/json' \
  -d '{ "enabled": false, "rotateSecret": true }'

curl -X DELETE "$DURABLEX_ENGINE_URL/webhook-endpoints/<id>" -H 'authorization: Bearer <secret-key>'

Endpoint delivery stats

GET /webhook-endpoints/stats rolls up the delivery log per endpoint so the console can show delivery health without a stored health field. An optional since (RFC3339) bounds the window; absent, it defaults to the last 30 days.

FieldMeaning
endpointIdThe endpoint the row aggregates.
deliveredTotal deliveries in the window (including in-flight).
succeededDeliveries that settled successfully.
failedDeliveries that terminally failed (exhausted or dead).
lastDeliveryThe most recent delivery's timestamp.
curl "$DURABLEX_ENGINE_URL/webhook-endpoints/stats?since=2026-06-01T00:00:00Z"

On this page