Webhook endpoints
Operations tagged Webhook endpoints in the public API.
About Webhook endpoints
A webhook endpoint is an HTTPS URL of yours that receives events as signed POST requests. It subscribes to event types (enabled_events, or * for every type), and each matching event becomes one delivery to it.
Signing. Every delivery carries Agentency-Signature: t={unix},v1={hex}: HMAC-SHA256 of {t}.{raw body}, keyed with the endpoint's signing secret. Reject a t more than 300 seconds from now and accept when any v1 matches. The secret (whsec_…) is returned once — by POST /webhook_endpoints and by POST /webhook_endpoints/{id}/rotate_secret — and reads show only secret_hint. After a rotation the previous secret keeps signing, as a second v1, for 24 hours.
Lifecycle. An endpoint stays enabled until you disable it (PATCH with status: disabled) or 20 deliveries in a row fail. Then it is disabled automatically, and webhook_endpoint.disabled goes to your other endpoints. A disabled endpoint is sent nothing: deliveries due to it fail without a request. Setting status: enabled again clears the failure count. An account has up to 50 endpoints by default, one per URL. Managing endpoints emits no events; POST /webhook_endpoints/{id}/test records a ping.
A key restricted to selected agents cannot manage endpoints: 403 FORBIDDEN.
IDs you will meet.
id— the endpoint id, an integer, in every/webhook_endpoints/{id}path; a delivery carries it asendpoint_id.
IDs on this page
- Webhook endpoint id
<ENDPOINT_ID> - Integer.
Get it from
- GET /v1/webhook_endpoints returns it as
data[].id - POST /v1/webhook_endpoints returns it as
id - GET /v1/webhook_deliveries/{id} returns it as
endpoint_id - Webhook webhook_endpoint.disabled carries it as
data.object.id
- GET /v1/webhook_endpoints returns it as
List webhook endpoints
enabled or disabled ones. Each shows secret_hint, never the secret.- Required scope
- webhooks:read
- Idempotency
- Read-only
- Rate limits
Query parameters
statusstringnullableOptionalEndpoint status:enabledordisabled.Possible values
enableddisablednulllimitintegerOptionalPage size, clamped to 1–100.Default:
20Minimum:1Maximum:100starting_afterstringOptionalCursor from a previous page'snext_cursor: returns the page after it. A cursor minted for another list,sortororderis 422INVALID_CURSOR.ending_beforestringOptionalCursor from a previous page'sprevious_cursor: returns the page before it. Wins when both cursors are sent.created[gte]stringOptionalOnly rows created on or after this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.created[lte]stringOptionalOnly rows created on or before this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.
Responses
- 200Success.
objectstringRequiredAlways
listdataarray of WebhookEndpointResourceRequiredhas_morebooleanRequirednext_cursorstringnullableRequiredprevious_cursorstringnullableRequiredurlstringRequiredAlways
/v1/webhook_endpoints
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDFORBIDDENINSUFFICIENT_SCOPE
- 422Validation failed. See
error.details. - 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X GET 'https://api.agentency.com/v1/webhook_endpoints' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "list",
"data": [
{
"object": "webhook_endpoint",
"id": 1,
"url": "https://example.com/webhooks/agentency",
"description": "Test endpoint",
"mode": "live",
"enabled_events": [
"ping"
],
"status": "enabled",
"api_version": "2026-08-14",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"disabled_at": null,
"disabled_reason": null,
"secret_hint": "whsec_…J0sA",
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00"
}
],
"has_more": false,
"next_cursor": null,
"previous_cursor": null,
"url": "/v1/webhook_endpoints"
}Create a webhook endpoint
enabled_events (* for every type), from the next event on. The response carries the signing secret (secret, whsec_…) — the only time it is returned: store it, then verify every delivery's Agentency-Signature with it. If you lose it, rotate it.
The URL is checked before it is stored: a private, internal or this API's own address is 422, and a URL the account already registered is 409 ENDPOINT_URL_ALREADY_REGISTERED. An account has up to 50 endpoints by default (422 WEBHOOK_ENDPOINT_LIMIT_REACHED, with details.max). A retry with the same Idempotency-Key returns the same endpoint without secret and with secret_already_shown: true: if you lost the first response, rotate the secret.- Required scope
- webhooks:write
- Idempotency
- Accepts Idempotency-Key
- Webhook events
- None
- Rate limits
Header parameters
Idempotency-KeystringOptionalMakes a retry safe. The first 2xx answer is stored for 24 h and replayed for the same key withIdempotency-Replayed: true; a retry while the first request runs is 409IDEMPOTENCY_IN_FLIGHT; the same key with a different payload is 422IDEMPOTENCY_KEY_REUSED; a malformed key is 400INVALID_IDEMPOTENCY_KEY.Min length:
16Max length:128Pattern:^[A-Za-z0-9_-]+$
Request body
urlstringRequiredHTTPS URL, whatever the mode. Validated against SSRF and DNS pinning rules; the API host itself is refused. A plain-http loopback URL is accepted only for atestendpoint on a deployment that allows insecure test URLs (local development).Format:
uriMax length:2048descriptionstringnullableOptionalYour note on what the endpoint is for, at most 255 characters.Max length:
255modestringOptionallive(default) ortest. A label for your own bookkeeping: both modes receive the same events and both require HTTPS.Possible values
livetestenabled_eventsarray of stringRequiredThe event types to receive, fromGET /event_types, or["*"]for every type, including types added later. At least one; an unknown type is 422VALIDATION_FAILED.Min items:
1
Responses
- 201
WebhookEndpointResourceobjectstringRequiredAlways
webhook_endpointidintegerRequiredThe endpoint id.urlstringRequiredWhere deliveries are POSTed. Unique within the account.descriptionstringnullableRequiredYour note on what the endpoint is for;nullwhen none.modestringRequiredliveortest: a label for your own bookkeeping. Both receive the same events.Possible values
livetestenabled_eventsarray of stringRequiredThe event types the endpoint receives;*means every type.statusstringRequiredenabled, ordisabled: sent nothing, seedisabled_reason.Possible values
enableddisabledapi_versionstringRequiredThe API version current when the endpoint was created. Each event carries its ownapi_version.consecutive_failuresintegerRequiredFailed attempts since the last success, across its deliveries. At 20 (by default) the endpoint is disabled automatically.last_success_atstringnullableRequiredWhen an attempt last succeeded (ISO 8601, UTC);nullbefore the first.last_failure_atstringnullableRequiredWhen an attempt last failed (ISO 8601, UTC);nullbefore the first.disabled_atstringnullableRequiredWhen the endpoint was disabled (ISO 8601, UTC);nullwhile enabled.disabled_reasonstringnullableRequiredWhy it is disabled:consecutive_failures(automatically, after 20 failed attempts in a row),manual(you setstatus: disabled) oroperator(platform support).nullwhile enabled.Possible values
consecutive_failuresmanualoperatornullsecret_hintstringRequiredThe last four characters of the signing secret (whsec_…a1B2), to tell which secret your receiver holds.created_atstringnullableRequiredWhen the endpoint was created (ISO 8601, UTC).updated_atstringnullableRequiredWhen the endpoint last changed (ISO 8601, UTC).secretstringOptionalThe signing secret (whsec_and 32 characters). Only on the response that created or rotated it — store it then; it is never returned again.secret_already_shownbooleanOptionalOnly on anIdempotency-Keyreplay of a create or rotate, alwaystrue: the secret was returned by the first response and is never cached for a replay. Rotate with a new key to get one you can read.
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither. A mutation that honoursIdempotency-Keyalso returnsINVALID_IDEMPOTENCY_KEYwhen that header is not 16–128[A-Za-z0-9_-]characters.Error codesACCOUNT_SELECTOR_NOT_SUPPORTEDINVALID_IDEMPOTENCY_KEY
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDFORBIDDENINSUFFICIENT_SCOPE
- 409The request conflicts with the current resource state.
Error codesENDPOINT_URL_ALREADY_REGISTEREDIDEMPOTENCY_IN_FLIGHT
- 422Validation failed. See
error.details.Error codesBLOCKED_DESTINATIONHTTPS_REQUIREDIDEMPOTENCY_KEY_REUSEDINVALID_URLSAME_HOST_REJECTEDVALIDATION_FAILEDWEBHOOK_ENDPOINT_LIMIT_REACHED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X POST 'https://api.agentency.com/v1/webhook_endpoints' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"
-d '{
"url": "url",
"description": "description",
"mode": "mode",
"enabled_events": []
}'Response
Example 201 response
{
"object": "webhook_endpoint",
"id": 1,
"url": "https://hooks.example.com/agentency",
"description": "Production listener",
"mode": "live",
"enabled_events": [
"knowledge.dataset.trained",
"knowledge.dataset.failed",
"conversation.handoff.requested"
],
"status": "enabled",
"api_version": "2026-08-14",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"disabled_at": null,
"disabled_reason": null,
"secret_hint": "whsec_…J0sA",
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00",
"secret": "whsec_3kTqZ8vN1mW4xR7pL2cY9bH5dF6gJ0sA"
}Retrieve a webhook endpoint
consecutive_failures, last_success_at, last_failure_at).- Required scope
- webhooks:read
- Idempotency
- Read-only
- Rate limits
Integer.id(path)Webhook endpoint idPlaceholder in the samples:
<ENDPOINT_ID>Get it from
- GET /v1/webhook_endpoints returns it as
data[].id - POST /v1/webhook_endpoints returns it as
id - GET /v1/webhook_deliveries/{id} returns it as
endpoint_id - Webhook webhook_endpoint.disabled carries it as
data.object.id
- GET /v1/webhook_endpoints returns it as
Path parameters
idintegerRequiredThe webhook endpoint id.
Responses
- 200
WebhookEndpointResourceobjectstringRequiredAlways
webhook_endpointidintegerRequiredThe endpoint id.urlstringRequiredWhere deliveries are POSTed. Unique within the account.descriptionstringnullableRequiredYour note on what the endpoint is for;nullwhen none.modestringRequiredliveortest: a label for your own bookkeeping. Both receive the same events.Possible values
livetestenabled_eventsarray of stringRequiredThe event types the endpoint receives;*means every type.statusstringRequiredenabled, ordisabled: sent nothing, seedisabled_reason.Possible values
enableddisabledapi_versionstringRequiredThe API version current when the endpoint was created. Each event carries its ownapi_version.consecutive_failuresintegerRequiredFailed attempts since the last success, across its deliveries. At 20 (by default) the endpoint is disabled automatically.last_success_atstringnullableRequiredWhen an attempt last succeeded (ISO 8601, UTC);nullbefore the first.last_failure_atstringnullableRequiredWhen an attempt last failed (ISO 8601, UTC);nullbefore the first.disabled_atstringnullableRequiredWhen the endpoint was disabled (ISO 8601, UTC);nullwhile enabled.disabled_reasonstringnullableRequiredWhy it is disabled:consecutive_failures(automatically, after 20 failed attempts in a row),manual(you setstatus: disabled) oroperator(platform support).nullwhile enabled.Possible values
consecutive_failuresmanualoperatornullsecret_hintstringRequiredThe last four characters of the signing secret (whsec_…a1B2), to tell which secret your receiver holds.created_atstringnullableRequiredWhen the endpoint was created (ISO 8601, UTC).updated_atstringnullableRequiredWhen the endpoint last changed (ISO 8601, UTC).secretstringOptionalThe signing secret (whsec_and 32 characters). Only on the response that created or rotated it — store it then; it is never returned again.secret_already_shownbooleanOptionalOnly on anIdempotency-Keyreplay of a create or rotate, alwaystrue: the secret was returned by the first response and is never cached for a replay. Rotate with a new key to get one you can read.
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDFORBIDDENINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X GET 'https://api.agentency.com/v1/webhook_endpoints/<ENDPOINT_ID>' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "webhook_endpoint",
"id": 1,
"url": "https://example.com/webhooks/agentency",
"description": "Test endpoint",
"mode": "live",
"enabled_events": [
"ping"
],
"status": "enabled",
"api_version": "2026-08-14",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"disabled_at": null,
"disabled_reason": null,
"secret_hint": "whsec_…J0sA",
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00"
}Update a webhook endpoint
POST /webhook_endpoints/{id}/rotate_secret replaces it). enabled_events replaces the whole list. status: disabled stops deliveries (disabled_reason: manual) without an event; status: enabled turns the endpoint back on and clears its failure count and disabled_reason. Deliveries that failed while it was off are not re-sent: retry them one by one.- Required scope
- webhooks:write
- Idempotency
- Accepts Idempotency-Key
- Webhook events
- None
- Rate limits
Integer.id(path)Webhook endpoint idPlaceholder in the samples:
<ENDPOINT_ID>Get it from
- GET /v1/webhook_endpoints returns it as
data[].id - POST /v1/webhook_endpoints returns it as
id - GET /v1/webhook_deliveries/{id} returns it as
endpoint_id - Webhook webhook_endpoint.disabled carries it as
data.object.id
- GET /v1/webhook_endpoints returns it as
Path parameters
idintegerRequiredThe webhook endpoint id.
Header parameters
Idempotency-KeystringOptionalMakes a retry safe. The first 2xx answer is stored for 24 h and replayed for the same key withIdempotency-Replayed: true; a retry while the first request runs is 409IDEMPOTENCY_IN_FLIGHT; the same key with a different payload is 422IDEMPOTENCY_KEY_REUSED; a malformed key is 400INVALID_IDEMPOTENCY_KEY.Min length:
16Max length:128Pattern:^[A-Za-z0-9_-]+$
Request body
urlstringOptionalHTTPS URL, whatever the mode. Validated against SSRF and DNS pinning rules; the API host itself is refused. A plain-http loopback URL is accepted only for atestendpoint on a deployment that allows insecure test URLs (local development).Format:
uriMax length:2048descriptionstringnullableOptionalYour note on what the endpoint is for, at most 255 characters;nullclears it.Max length:
255modestringOptionalliveortest; omitted keeps the current mode. A label for your own bookkeeping: both modes receive the same events and both require HTTPS.Possible values
livetestenabled_eventsarray of stringOptionalThe event types to receive, replacing the whole list: types fromGET /event_types, or["*"]for every type. At least one.Min items:
1statusstringOptionaldisabledstops all deliveries to the endpoint (disabled_reason: manual);enabledturns it back on and resetsconsecutive_failures.Possible values
enableddisabled
Responses
- 200
WebhookEndpointResourceobjectstringRequiredAlways
webhook_endpointidintegerRequiredThe endpoint id.urlstringRequiredWhere deliveries are POSTed. Unique within the account.descriptionstringnullableRequiredYour note on what the endpoint is for;nullwhen none.modestringRequiredliveortest: a label for your own bookkeeping. Both receive the same events.Possible values
livetestenabled_eventsarray of stringRequiredThe event types the endpoint receives;*means every type.statusstringRequiredenabled, ordisabled: sent nothing, seedisabled_reason.Possible values
enableddisabledapi_versionstringRequiredThe API version current when the endpoint was created. Each event carries its ownapi_version.consecutive_failuresintegerRequiredFailed attempts since the last success, across its deliveries. At 20 (by default) the endpoint is disabled automatically.last_success_atstringnullableRequiredWhen an attempt last succeeded (ISO 8601, UTC);nullbefore the first.last_failure_atstringnullableRequiredWhen an attempt last failed (ISO 8601, UTC);nullbefore the first.disabled_atstringnullableRequiredWhen the endpoint was disabled (ISO 8601, UTC);nullwhile enabled.disabled_reasonstringnullableRequiredWhy it is disabled:consecutive_failures(automatically, after 20 failed attempts in a row),manual(you setstatus: disabled) oroperator(platform support).nullwhile enabled.Possible values
consecutive_failuresmanualoperatornullsecret_hintstringRequiredThe last four characters of the signing secret (whsec_…a1B2), to tell which secret your receiver holds.created_atstringnullableRequiredWhen the endpoint was created (ISO 8601, UTC).updated_atstringnullableRequiredWhen the endpoint last changed (ISO 8601, UTC).secretstringOptionalThe signing secret (whsec_and 32 characters). Only on the response that created or rotated it — store it then; it is never returned again.secret_already_shownbooleanOptionalOnly on anIdempotency-Keyreplay of a create or rotate, alwaystrue: the secret was returned by the first response and is never cached for a replay. Rotate with a new key to get one you can read.
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither. A mutation that honoursIdempotency-Keyalso returnsINVALID_IDEMPOTENCY_KEYwhen that header is not 16–128[A-Za-z0-9_-]characters.Error codesACCOUNT_SELECTOR_NOT_SUPPORTEDINVALID_IDEMPOTENCY_KEY
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDFORBIDDENINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 409The request conflicts with the current resource state.
Error codesENDPOINT_URL_ALREADY_REGISTEREDIDEMPOTENCY_IN_FLIGHT
- 422Validation failed. See
error.details.Error codesBLOCKED_DESTINATIONHTTPS_REQUIREDIDEMPOTENCY_KEY_REUSEDINVALID_URLSAME_HOST_REJECTEDVALIDATION_FAILED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X PATCH 'https://api.agentency.com/v1/webhook_endpoints/<ENDPOINT_ID>' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"
-d '{
"url": "url",
"description": "description",
"mode": "mode",
"enabled_events": [],
"status": "status"
}'Response
Example 200 response
{
"object": "webhook_endpoint",
"id": 1,
"url": "https://hooks.example.com/agentency",
"description": "Production listener",
"mode": "live",
"enabled_events": [
"*"
],
"status": "enabled",
"api_version": "2026-08-14",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"disabled_at": null,
"disabled_reason": null,
"secret_hint": "whsec_…J0sA",
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00"
}Delete a webhook endpoint
GET /webhook_deliveries/{id} until they are pruned.- Required scope
- webhooks:write
- Idempotency
- Accepts Idempotency-Key
- Webhook events
- None
- Rate limits
Integer.id(path)Webhook endpoint idPlaceholder in the samples:
<ENDPOINT_ID>Get it from
- GET /v1/webhook_endpoints returns it as
data[].id - POST /v1/webhook_endpoints returns it as
id - GET /v1/webhook_deliveries/{id} returns it as
endpoint_id - Webhook webhook_endpoint.disabled carries it as
data.object.id
- GET /v1/webhook_endpoints returns it as
Path parameters
idintegerRequiredThe webhook endpoint id.
Header parameters
Idempotency-KeystringOptionalMakes a retry safe. The first 2xx answer is stored for 24 h and replayed for the same key withIdempotency-Replayed: true; a retry while the first request runs is 409IDEMPOTENCY_IN_FLIGHT; the same key with a different payload is 422IDEMPOTENCY_KEY_REUSED; a malformed key is 400INVALID_IDEMPOTENCY_KEY.Min length:
16Max length:128Pattern:^[A-Za-z0-9_-]+$
Responses
- 200Success.
objectstringRequiredAlways
webhook_endpointidintegerRequiredThe deleted endpoint's id.deletedbooleanRequiredAlwaystrue.
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither. A mutation that honoursIdempotency-Keyalso returnsINVALID_IDEMPOTENCY_KEYwhen that header is not 16–128[A-Za-z0-9_-]characters.Error codesACCOUNT_SELECTOR_NOT_SUPPORTEDINVALID_IDEMPOTENCY_KEY
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDFORBIDDENINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 409The request conflicts with the current resource state.
Error codesIDEMPOTENCY_IN_FLIGHT
- 422Validation failed. See
error.details.Error codesIDEMPOTENCY_KEY_REUSED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X DELETE 'https://api.agentency.com/v1/webhook_endpoints/<ENDPOINT_ID>' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "webhook_endpoint",
"id": 1,
"deleted": true
}List an endpoint's deliveries
status: the event, the attempts made and the latest attempt's response. status=failed lists what to retry with POST /webhook_deliveries/{id}/retry.- Required scope
- webhooks:read
- Idempotency
- Read-only
- Rate limits
Integer.id(path)Webhook endpoint idPlaceholder in the samples:
<ENDPOINT_ID>Get it from
- GET /v1/webhook_endpoints returns it as
data[].id - POST /v1/webhook_endpoints returns it as
id - GET /v1/webhook_deliveries/{id} returns it as
endpoint_id - Webhook webhook_endpoint.disabled carries it as
data.object.id
- GET /v1/webhook_endpoints returns it as
Path parameters
idintegerRequiredThe webhook endpoint id.
Query parameters
statusstringnullableOptionalDelivery status:pending,processing,deliveredorfailed.Possible values
pendingprocessingdeliveredfailednulllimitintegerOptionalPage size, clamped to 1–100.Default:
20Minimum:1Maximum:100starting_afterstringOptionalCursor from a previous page'snext_cursor: returns the page after it. A cursor minted for another list,sortororderis 422INVALID_CURSOR.ending_beforestringOptionalCursor from a previous page'sprevious_cursor: returns the page before it. Wins when both cursors are sent.created[gte]stringOptionalOnly rows created on or after this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.created[lte]stringOptionalOnly rows created on or before this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.
Responses
- 200Success.
objectstringRequiredAlways
listdataarray of WebhookDeliveryResourceRequiredhas_morebooleanRequirednext_cursorstringnullableRequiredprevious_cursorstringnullableRequiredurlstringRequired
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDFORBIDDENINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 422Validation failed. See
error.details. - 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X GET 'https://api.agentency.com/v1/webhook_endpoints/<ENDPOINT_ID>/deliveries' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "list",
"data": [
{
"object": "webhook_delivery",
"id": 1,
"event_id": "evt_example",
"endpoint_id": 1,
"event_type": "ping",
"status": "pending",
"attempts": 0,
"scheduled_at": "2026-01-01T00:00:00+00:00",
"processed_at": null,
"response_status": null,
"response_body_snippet": null,
"duration_ms": null,
"created_at": "2026-01-01T00:00:00+00:00"
}
],
"has_more": false,
"next_cursor": null,
"previous_cursor": null,
"url": "/v1/webhook_endpoints/1/deliveries"
}Rotate a webhook signing secret
secret — the only time it is returned. For 24 hours every delivery is also signed with the previous secret (a second v1= value in Agentency-Signature), so receivers can switch over without dropping events. Rotating again inside that window replaces only the current secret: the oldest unexpired previous secret keeps signing until its original expiry. The endpoint URL and subscriptions are unchanged. A retry with the same Idempotency-Key does not rotate again; it answers without secret and with secret_already_shown: true — to get a secret you can read, rotate with a new key.- Required scope
- webhooks:write
- Idempotency
- Accepts Idempotency-Key
- Webhook events
- None
- Rate limits
Integer.id(path)Webhook endpoint idPlaceholder in the samples:
<ENDPOINT_ID>Get it from
- GET /v1/webhook_endpoints returns it as
data[].id - POST /v1/webhook_endpoints returns it as
id - GET /v1/webhook_deliveries/{id} returns it as
endpoint_id - Webhook webhook_endpoint.disabled carries it as
data.object.id
- GET /v1/webhook_endpoints returns it as
Path parameters
idintegerRequiredThe webhook endpoint id.
Header parameters
Idempotency-KeystringOptionalMakes a retry safe. The first 2xx answer is stored for 24 h and replayed for the same key withIdempotency-Replayed: true; a retry while the first request runs is 409IDEMPOTENCY_IN_FLIGHT; the same key with a different payload is 422IDEMPOTENCY_KEY_REUSED; a malformed key is 400INVALID_IDEMPOTENCY_KEY.Min length:
16Max length:128Pattern:^[A-Za-z0-9_-]+$
Responses
- 200
WebhookEndpointResourceobjectstringRequiredAlways
webhook_endpointidintegerRequiredThe endpoint id.urlstringRequiredWhere deliveries are POSTed. Unique within the account.descriptionstringnullableRequiredYour note on what the endpoint is for;nullwhen none.modestringRequiredliveortest: a label for your own bookkeeping. Both receive the same events.Possible values
livetestenabled_eventsarray of stringRequiredThe event types the endpoint receives;*means every type.statusstringRequiredenabled, ordisabled: sent nothing, seedisabled_reason.Possible values
enableddisabledapi_versionstringRequiredThe API version current when the endpoint was created. Each event carries its ownapi_version.consecutive_failuresintegerRequiredFailed attempts since the last success, across its deliveries. At 20 (by default) the endpoint is disabled automatically.last_success_atstringnullableRequiredWhen an attempt last succeeded (ISO 8601, UTC);nullbefore the first.last_failure_atstringnullableRequiredWhen an attempt last failed (ISO 8601, UTC);nullbefore the first.disabled_atstringnullableRequiredWhen the endpoint was disabled (ISO 8601, UTC);nullwhile enabled.disabled_reasonstringnullableRequiredWhy it is disabled:consecutive_failures(automatically, after 20 failed attempts in a row),manual(you setstatus: disabled) oroperator(platform support).nullwhile enabled.Possible values
consecutive_failuresmanualoperatornullsecret_hintstringRequiredThe last four characters of the signing secret (whsec_…a1B2), to tell which secret your receiver holds.created_atstringnullableRequiredWhen the endpoint was created (ISO 8601, UTC).updated_atstringnullableRequiredWhen the endpoint last changed (ISO 8601, UTC).secretstringOptionalThe signing secret (whsec_and 32 characters). Only on the response that created or rotated it — store it then; it is never returned again.secret_already_shownbooleanOptionalOnly on anIdempotency-Keyreplay of a create or rotate, alwaystrue: the secret was returned by the first response and is never cached for a replay. Rotate with a new key to get one you can read.
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither. A mutation that honoursIdempotency-Keyalso returnsINVALID_IDEMPOTENCY_KEYwhen that header is not 16–128[A-Za-z0-9_-]characters.Error codesACCOUNT_SELECTOR_NOT_SUPPORTEDINVALID_IDEMPOTENCY_KEY
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDFORBIDDENINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 409The request conflicts with the current resource state.
Error codesIDEMPOTENCY_IN_FLIGHT
- 422Validation failed. See
error.details.Error codesIDEMPOTENCY_KEY_REUSED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X POST 'https://api.agentency.com/v1/webhook_endpoints/<ENDPOINT_ID>/rotate_secret' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "webhook_endpoint",
"id": 1,
"url": "https://hooks.example.com/agentency",
"description": "Production listener",
"mode": "live",
"enabled_events": [
"*"
],
"status": "enabled",
"api_version": "2026-08-14",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"disabled_at": null,
"disabled_reason": null,
"secret_hint": "whsec_…J0sA",
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00",
"secret": "whsec_3kTqZ8vN1mW4xR7pL2cY9bH5dF6gJ0sA"
}Send a test ping
ping event and queues one delivery of it to this endpoint only, whether or not it subscribes to ping — signed, retried and listed like any other delivery. Its data.object is {"object": "ping", "endpoint_id": …}. A disabled endpoint's ping fails without a request. Shares a budget of 20 calls a minute per API key with delivery retries.- Required scope
- webhooks:write
- Idempotency
- Accepts Idempotency-Key
- Webhook events
- ping
- Rate limits
Integer.id(path)Webhook endpoint idPlaceholder in the samples:
<ENDPOINT_ID>Get it from
- GET /v1/webhook_endpoints returns it as
data[].id - POST /v1/webhook_endpoints returns it as
id - GET /v1/webhook_deliveries/{id} returns it as
endpoint_id - Webhook webhook_endpoint.disabled carries it as
data.object.id
- GET /v1/webhook_endpoints returns it as
Path parameters
idintegerRequiredThe webhook endpoint id.
Header parameters
Idempotency-KeystringOptionalMakes a retry safe. The first 2xx answer is stored for 24 h and replayed for the same key withIdempotency-Replayed: true; a retry while the first request runs is 409IDEMPOTENCY_IN_FLIGHT; the same key with a different payload is 422IDEMPOTENCY_KEY_REUSED; a malformed key is 400INVALID_IDEMPOTENCY_KEY.Min length:
16Max length:128Pattern:^[A-Za-z0-9_-]+$
Responses
- 200Success.
objectstringRequiredAlways
webhook_testevent_idstringnullableRequiredThepingevent recorded for the test (evt_…): find its delivery inGET /webhook_endpoints/{id}/deliveries.nullonly while webhooks are switched off platform-wide, when nothing is recorded or sent.endpoint_idintegerRequiredThe endpoint the ping goes to — this one only, whatever it subscribes to.
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither. A mutation that honoursIdempotency-Keyalso returnsINVALID_IDEMPOTENCY_KEYwhen that header is not 16–128[A-Za-z0-9_-]characters.Error codesACCOUNT_SELECTOR_NOT_SUPPORTEDINVALID_IDEMPOTENCY_KEY
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDFORBIDDENINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 409The request conflicts with the current resource state.
Error codesIDEMPOTENCY_IN_FLIGHT
- 422Validation failed. See
error.details.Error codesIDEMPOTENCY_KEY_REUSED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X POST 'https://api.agentency.com/v1/webhook_endpoints/<ENDPOINT_ID>/test' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "webhook_test",
"event_id": "evt_example",
"endpoint_id": 1
}