Browse the docs

Reference · 8 operations

Webhooks

Signed deliveries of the event feed to your own endpoints, retried for 24 hours. A convenience: GET /events is the source of truth.

Last updated from API version 2026-10-06 at (live spec, refreshed hourly). openapi.json

Webhook endpoints

webhookEndpoints.list

The workspace's webhook endpoints, newest first.

get/api/v1/webhook-endpoints
Scope
write
Credits
Free
Idempotency-Key
Not used

Never includes the signing secret (only secretHint).

Parameters

  • limitqueryinteger

    Page size.

    default 50min 1max 200
  • cursorquerystring

    Opaque cursor from meta.page.nextCursor. Reusing it with other filters is 400 CURSOR_MISMATCH.

Responses

200
OK.
Show schema

WebhookEndpointListResponse

Errors

400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.

curl "https://app.owlbound.ai/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer $OWLBOUND_API_KEY" \
  -H "Owlbound-Version: 2026-10-06"

webhookEndpoints.create

Add a webhook endpoint; the signing secret is in this answer only.

post/api/v1/webhook-endpoints
Scope
write
Credits
Free
Idempotency-Key
optional

url must be https or http on a public host: names are resolved and private, loopback, link-local and metadata addresses are refused (422 HOST_NOT_ALLOWED), at create and on every delivery. Redirects are never followed: answer the POST directly. events lists the event types to receive (["*"] = all of them, also future ones). The answer carries secret (owl_whsec_...): store it, it is never shown again (rotate it with rotate-secret). At most 20 endpoints per workspace.

Each delivery is a POST of the event (the GET /events item) as JSON, with Owlbound-Event-Id (dedupe on it: an event can arrive more than once), Owlbound-Delivery-Id and Owlbound-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, t + "." + body)> (one v1 per active secret during a rotation). Reject a t more than 5 minutes old. A 2xx answer within 10 seconds is delivered; anything else is retried with backoff (1 min, 5 min, 30 min, 2 h, 5 h, 8 h, 8 h: 8 attempts over about 24 hours), then failed. An endpoint that fails every delivery for 3 days is disabled and its owner is emailed. Webhooks are a convenience: GET /events is the source of truth.

Verify a delivery (Node; rawBody is the body as received, before parsing):

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received (a string, before JSON.parse).
export function verifyOwlboundWebhook(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = header.split(",").map((p) => p.trim().split("="));
  const t = Number(parts.find(([k]) => k === "t")?.[1]);
  const signatures = parts.filter(([k]) => k === "v1").map(([, v]) => v);
  if (!Number.isInteger(t) || signatures.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false; // replay window
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((hex) => {
    const given = Buffer.from(hex, "hex");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}
// Then dedupe on the Owlbound-Event-Id header: an event can arrive more than once.

Parameters

  • Idempotency-Keyheaderstring

    1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says x-owlbound-idempotent: required (missing: 400 VALIDATION_ERROR, param: Idempotency-Key). Same key and body after completion: the stored response is replayed with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-After. Same key, other body or operation: 422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a 402 INSUFFICIENT_CREDITS replays even after a top-up, so use a new Idempotency-Key after adding credits.

    min length 1max length 255

Request body (required) · application/json

WebhookEndpointCreateRequest

  • urlstringrequired
    max length 2048
  • eventsarray of WebhookEventTyperequired
    *action.startedaction.target.completedaction.target.failedaction.completedaction.pending_approvalapproval.decidedmin items 1
  • descriptionstring
    max length 500

Responses

200
OK.
Show schema

WebhookEndpointCreatedResponse

Errors

400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still running; `Retry-After` is the lease left, at most 60 s) or IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to store; `details.originalRequestId`). · 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.

curl -X POST "https://app.owlbound.ai/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer $OWLBOUND_API_KEY" \
  -H "Owlbound-Version: 2026-10-06" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com",
  "events": [
    "*"
  ]
}'

webhookEndpoints.get

One webhook endpoint (without its secret).

get/api/v1/webhook-endpoints/{id}
Scope
write
Credits
Free
Idempotency-Key
Not used

Parameters

  • idpathstringrequired

    The webhook endpoint id.

Responses

200
OK.
Show schema

WebhookEndpointResponse

Errors

400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.

curl "https://app.owlbound.ai/api/v1/webhook-endpoints/$ID" \
  -H "Authorization: Bearer $OWLBOUND_API_KEY" \
  -H "Owlbound-Version: 2026-10-06"

webhookEndpoints.update

Change an endpoint's url, events, description or status.

patch/api/v1/webhook-endpoints/{id}
Scope
write
Credits
Free
Idempotency-Key
optional

A new url is checked like at create. status: "disabled" stops deliveries (queued ones are canceled when due); status: "enabled" turns it back on and resets its failure count.

Parameters

  • idpathstringrequired

    The webhook endpoint id.

  • Idempotency-Keyheaderstring

    1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says x-owlbound-idempotent: required (missing: 400 VALIDATION_ERROR, param: Idempotency-Key). Same key and body after completion: the stored response is replayed with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-After. Same key, other body or operation: 422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a 402 INSUFFICIENT_CREDITS replays even after a top-up, so use a new Idempotency-Key after adding credits.

    min length 1max length 255

Request body (required) · application/json

WebhookEndpointUpdateRequest

  • urlstring
    max length 2048
  • eventsarray of WebhookEventType
    *action.startedaction.target.completedaction.target.failedaction.completedaction.pending_approvalapproval.decidedmin items 1
  • descriptionstring | null
    max length 500
  • statusstring
    enableddisabled

Responses

200
OK.
Show schema

WebhookEndpointResponse

Errors

400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still running; `Retry-After` is the lease left, at most 60 s) or IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to store; `details.originalRequestId`). · 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.

curl -X PATCH "https://app.owlbound.ai/api/v1/webhook-endpoints/$ID" \
  -H "Authorization: Bearer $OWLBOUND_API_KEY" \
  -H "Owlbound-Version: 2026-10-06" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com",
  "events": [
    "*"
  ],
  "description": "Created from the API"
}'

webhookEndpoints.delete

Delete an endpoint; its queued deliveries are canceled.

delete/api/v1/webhook-endpoints/{id}
Scope
write
Credits
Free
Idempotency-Key
optional

Parameters

  • idpathstringrequired

    The webhook endpoint id.

  • Idempotency-Keyheaderstring

    1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says x-owlbound-idempotent: required (missing: 400 VALIDATION_ERROR, param: Idempotency-Key). Same key and body after completion: the stored response is replayed with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-After. Same key, other body or operation: 422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a 402 INSUFFICIENT_CREDITS replays even after a top-up, so use a new Idempotency-Key after adding credits.

    min length 1max length 255

Responses

200
OK.
Show schema

WebhookEndpointDeletedResponse

  • dataobjectrequired
    0
    + Show child fields
    • idstringrequired
      0
    • deletedbooleanrequired
      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

Errors

400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still running; `Retry-After` is the lease left, at most 60 s) or IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to store; `details.originalRequestId`). · 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.

curl -X DELETE "https://app.owlbound.ai/api/v1/webhook-endpoints/$ID" \
  -H "Authorization: Bearer $OWLBOUND_API_KEY" \
  -H "Owlbound-Version: 2026-10-06" \
  -H "Idempotency-Key: $(uuidgen)"

webhookEndpoints.rotateSecret

Make a new signing secret; the previous one keeps signing for 24 hours.

post/api/v1/webhook-endpoints/{id}/rotate-secret
Scope
write
Credits
Free
Idempotency-Key
optional

The answer carries the new secret (shown once) and previousSecretExpiresAt. Until then every delivery is signed with both secrets (two v1 values), so receivers can switch without a gap. A second rotation within the 24 hours drops the oldest secret.

Parameters

  • idpathstringrequired

    The webhook endpoint id.

  • Idempotency-Keyheaderstring

    1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says x-owlbound-idempotent: required (missing: 400 VALIDATION_ERROR, param: Idempotency-Key). Same key and body after completion: the stored response is replayed with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-After. Same key, other body or operation: 422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a 402 INSUFFICIENT_CREDITS replays even after a top-up, so use a new Idempotency-Key after adding credits.

    min length 1max length 255

Responses

200
OK.
Show schema

WebhookEndpointCreatedResponse

Errors

400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still running; `Retry-After` is the lease left, at most 60 s) or IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to store; `details.originalRequestId`). · 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.

curl -X POST "https://app.owlbound.ai/api/v1/webhook-endpoints/$ID/rotate-secret" \
  -H "Authorization: Bearer $OWLBOUND_API_KEY" \
  -H "Owlbound-Version: 2026-10-06" \
  -H "Idempotency-Key: $(uuidgen)"

Deliveries

webhookEndpoints.deliveries.list

An endpoint's deliveries, newest first (kept 7 days).

get/api/v1/webhook-endpoints/{id}/deliveries
Scope
write
Credits
Free
Idempotency-Key
Not used

Each delivery shows its status, attempts, the last response code and the first 1 KB of the answer.

Parameters

  • idpathstringrequired

    The webhook endpoint id.

  • limitqueryinteger

    Page size.

    default 50min 1max 200
  • cursorquerystring

    Opaque cursor from meta.page.nextCursor. Reusing it with other filters is 400 CURSOR_MISMATCH.

  • statusquerystring

    Only deliveries in this status: pending, sending, delivered, failed or canceled.

Responses

200
OK.
Show schema

WebhookDeliveryListResponse

Errors

400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.

curl "https://app.owlbound.ai/api/v1/webhook-endpoints/$ID/deliveries" \
  -H "Authorization: Bearer $OWLBOUND_API_KEY" \
  -H "Owlbound-Version: 2026-10-06"

webhookEndpoints.deliveries.redeliver

Send a delivery's event again.

post/api/v1/webhook-endpoints/{id}/deliveries/{deliveryId}/redeliver
Scope
write
Credits
Free
Idempotency-Key
optional

Queues the same event (same Owlbound-Event-Id, fresh signature) for this endpoint; it is sent within a minute. While a redelivery of that event is still pending, the same one is returned. A disabled endpoint is 409 WEBHOOK_ENDPOINT_DISABLED.

Parameters

  • idpathstringrequired

    The webhook endpoint id.

  • deliveryIdpathstringrequired

    The delivery id.

  • Idempotency-Keyheaderstring

    1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says x-owlbound-idempotent: required (missing: 400 VALIDATION_ERROR, param: Idempotency-Key). Same key and body after completion: the stored response is replayed with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-After. Same key, other body or operation: 422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a 402 INSUFFICIENT_CREDITS replays even after a top-up, so use a new Idempotency-Key after adding credits.

    min length 1max length 255

Responses

200
OK.
Show schema

WebhookDeliveryResponse

Errors

400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still running; `Retry-After` is the lease left, at most 60 s) or IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to store; `details.originalRequestId`). · 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.

curl -X POST "https://app.owlbound.ai/api/v1/webhook-endpoints/$ID/deliveries/$DELIVERY_ID/redeliver" \
  -H "Authorization: Bearer $OWLBOUND_API_KEY" \
  -H "Owlbound-Version: 2026-10-06" \
  -H "Idempotency-Key: $(uuidgen)"

Models on this page

Shared objects the operations above link to. Clients must ignore fields they do not know.

WebhookEndpointobject
  • idstringrequired
    0
  • urlstringrequired
    0
  • descriptionstring | nullrequired
    0
  • eventsarray of WebhookEventTyperequired
    *action.startedaction.target.completedaction.target.failedaction.completedaction.pending_approvalapproval.decided
  • statusstringrequired
    enableddisabled
  • disabledReasonstring | nullrequired
    manualfailing
  • secretHintstring | nullrequired

    owl_whsec_... and the last 4 characters of the current secret.

    0
  • previousSecretExpiresAtnumber | nullrequired

    During a rotation: when the previous secret stops signing.

    0
  • consecutiveFailuresintegerrequired
    0
  • failingSincenumber | nullrequired
    0
  • lastSuccessAtnumber | nullrequired
    0
  • lastFailureAtnumber | nullrequired
    0
  • createdAtnumberrequired
    0
  • updatedAtnumberrequired
    0
PagedResponseMetaobject

Metadata on a paginated success response.

  • requestIdstringrequired
    0
  • creditsCreditsMetarequired
    0
  • pagePageMetarequired

    Cursor pagination. hasMore is the only end signal; pages may be short.

    0
CreditsMetaobject
  • usednumberrequired

    Credits charged by this request. Reads are free.

    0
  • balancenumber
    0
  • estimatednumber

    Dry run only (action operations).

    0
  • availablenumber

    Dry run only (action operations).

    0
  • reservationIdstring | null

    Async operations only; the job settles this reservation.

    0
PageMetaobject

Cursor pagination. hasMore is the only end signal; pages may be short.

  • nextCursorstring | nullrequired
    0
  • hasMorebooleanrequired
    0
  • limitintegerrequired
    min 1max 200
WebhookEndpointWithSecretobject
  • secretstringrequired

    The signing secret. Shown only in this answer.

    0
  • idstringrequired
    0
  • urlstringrequired
    0
  • descriptionstring | nullrequired
    0
  • eventsarray of WebhookEventTyperequired
    *action.startedaction.target.completedaction.target.failedaction.completedaction.pending_approvalapproval.decided
  • statusstringrequired
    enableddisabled
  • disabledReasonstring | nullrequired
    manualfailing
  • secretHintstring | nullrequired

    owl_whsec_... and the last 4 characters of the current secret.

    0
  • previousSecretExpiresAtnumber | nullrequired

    During a rotation: when the previous secret stops signing.

    0
  • consecutiveFailuresintegerrequired
    0
  • failingSincenumber | nullrequired
    0
  • lastSuccessAtnumber | nullrequired
    0
  • lastFailureAtnumber | nullrequired
    0
  • createdAtnumberrequired
    0
  • updatedAtnumberrequired
    0
ResponseMetaobject

Metadata on every success response.

  • requestIdstringrequired
    0
  • creditsCreditsMetarequired
    0
  • Cursor pagination. hasMore is the only end signal; pages may be short.

    0
  • dryRunboolean

    True only on a dry-run response (action operations).

    0
WebhookDeliveryobject
  • idstringrequired
    0
  • endpointIdstringrequired
    0
  • eventIdstringrequired
    0
  • eventTypestringrequired
    0
  • statusstringrequired
    pendingsendingdeliveredfailedcanceled
  • attemptsintegerrequired
    0
  • maxAttemptsintegerrequired
    0
  • nextAttemptAtnumber | nullrequired
    0
  • lastAttemptAtnumber | nullrequired
    0
  • responseStatusinteger | nullrequired
    0
  • responseBodystring | nullrequired

    The first 1 KB of the last answer.

    0
  • errorstring | nullrequired
    0
  • durationMsnumber | nullrequired
    0
  • redeliveryOfstring | nullrequired
    0
  • deliveredAtnumber | nullrequired
    0
  • createdAtnumberrequired
    0

← All operations