# Webhook endpoints

A webhook endpoint is a URL on a service of yours that a pipeline's webhook block can ask for an outcome.

A webhook block posts the request to its endpoint, and the endpoint answers
with `next`, `human`, `approve`, or `deny`. Deliveries are signed with the
endpoint's secret, returned once when its first revision is created. Verify the
`WithHuman-Signature` header with it.

To change an endpoint, [create a revision](#createWebhookEndpointRevision),
[test it](#testWebhookEndpointRevision), then
[activate it](#activateWebhookEndpointRevision). URLs must be public HTTPS.

## List webhook endpoints

`GET /api/v1/webhook_endpoints`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.read`

Returns every webhook endpoint in the organization, one entry per key. Each entry carries the latest revision's name and URL, the active revision number, and the active pipeline revisions that post to it. Signing secrets are never included.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | How many endpoints to return. Defaults to 200. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 200 | object | The endpoints |
| 401 | ErrorResponse | You are not signed in |
| 403 | ErrorResponse | You cannot read webhook endpoints |
| 501 | ErrorResponse | This deployment has no secrets key, so webhook endpoints are unavailable |

Response body (200):

- `webhook_endpoints` · array<WebhookEndpointSummary> · required
  - `endpoint_key` · string · required
  - `name` · string · required: The name from the latest revision.
  - `url` · uri · required: The URL from the latest revision.
  - `active_revision` · int64: The revision deliveries go to. Absent while the endpoint is archived or no revision has been activated yet.
  - `archived_at` · date-time: When the endpoint was archived. Absent for a live endpoint, including one whose revisions are all drafts. Cleared when a revision is activated again.
  - `latest_revision` · int64 · required: The newest revision, active or not.
  - `revision_count` · int64 · required
  - `latest_created_at` · date-time · required: When the newest revision was created.
  - `secret_rotated_at` · date-time · required: When the signing secret was last created or rotated.
  - `uses` · array<PipelineUse> · required: The active pipeline revisions that post to the endpoint. Empty when it can be archived.
    - `pipeline_id` · uuid · required
    - `scope` · enum · required: The pipeline's scope. One of `organization`, `agent`.
    - `agent_slug` · string: The agent's slug. Present for the `agent` scope only.
    - `revision` · int64 · required: The active revision number.
    - `block_key` · string: The block that uses the object. Absent when the revision names an escalation path as its default.

### Example

```bash
curl -X GET "$WITHHUMAN_URL/api/v1/webhook_endpoints" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
```

200 response

```json
{
  "webhook_endpoints": [
    {
      "endpoint_key": "string",
      "name": "string",
      "url": "https://example.com/webhooks/withhuman",
      "active_revision": 1,
      "archived_at": "2026-09-08T12:02:11Z",
      "latest_revision": 1,
      "revision_count": 1,
      "latest_created_at": "2026-09-08T12:02:11Z",
      "secret_rotated_at": "2026-09-08T12:02:11Z",
      "uses": [
        {
          "pipeline_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
          "scope": "organization",
          "agent_slug": "string",
          "revision": 1,
          "block_key": "string"
        }
      ]
    }
  ]
}
```

## Retrieve a webhook endpoint

`GET /api/v1/webhook_endpoints/{endpoint_key}`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.read`

Returns the endpoint's active revision. This is where deliveries go right now.

The `ETag` header carries the active revision number. Pass it as `If-Match` when you activate another revision or archive the endpoint.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 200 | WebhookEndpointRevision | The active revision |
| 401 | ErrorResponse | You are not signed in |
| 403 | ErrorResponse | You cannot read webhook endpoints |
| 404 | ErrorResponse | No such endpoint, or no revision is active |

Response body (200):

- `id` · uuid · required
- `endpoint_key` · string · required
- `revision` · int64 · required: The revision number. Revisions count up from 1.
- `is_active` · boolean · required: Whether deliveries go to this revision.
- `name` · string · required: The display name.
- `url` · uri · required: Where deliveries go.
- `created_by_actor_id` · uuid · required: The membership or organization-key actor that created the revision.
- `created_at` · date-time · required
- `signing_secret` · string: Present only in the response that created the endpoint's first revision. Starts with `whsec_`. Your receiver uses it to verify the `WithHuman-Signature` header.

### Example

```bash
curl -X GET "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
```

200 response

```json
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "endpoint_key": "string",
  "revision": 1,
  "is_active": true,
  "name": "Fraud check",
  "url": "https://fraud.example.com/approvals",
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "signing_secret": "whsec_9f2c1b7e4d3a4f8b"
}
```

## List webhook endpoint revisions

`GET /api/v1/webhook_endpoints/{endpoint_key}/revisions`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.read`

Returns the endpoint's revisions, newest first. Signing secrets are never included.

Paging is by cursor. When more revisions follow, the response carries `next_cursor`. Pass it back as `cursor` to get the next page. The last page has no `next_cursor`. `total_count` is how many revisions there are across every page.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |
| `cursor` | query | string | no | The `next_cursor` from the previous page. |
| `limit` | query | integer | no | How many revisions to return per page. Defaults to 50. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 200 | object | The revisions, newest first |
| 400 | ErrorResponse | `cursor` is not a cursor this endpoint issued |
| 401 | ErrorResponse | You are not signed in |
| 403 | ErrorResponse | You cannot read webhook endpoints |

Response body (200):

- `revisions` · array<WebhookEndpointRevision> · required: The revisions on this page, newest first.
  - `id` · uuid · required
  - `endpoint_key` · string · required
  - `revision` · int64 · required: The revision number. Revisions count up from 1.
  - `is_active` · boolean · required: Whether deliveries go to this revision.
  - `name` · string · required: The display name.
  - `url` · uri · required: Where deliveries go.
  - `created_by_actor_id` · uuid · required: The membership or organization-key actor that created the revision.
  - `created_at` · date-time · required
  - `signing_secret` · string: Present only in the response that created the endpoint's first revision. Starts with `whsec_`. Your receiver uses it to verify the `WithHuman-Signature` header.
- `next_cursor` · string: Present when another page follows.
- `total_count` · int64 · required: How many revisions the endpoint has, across every page.

### Example

```bash
curl -X GET "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}/revisions" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
```

200 response

```json
{
  "revisions": [
    {
      "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "endpoint_key": "string",
      "revision": 1,
      "is_active": true,
      "name": "Fraud check",
      "url": "https://fraud.example.com/approvals",
      "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "created_at": "2026-09-08T12:02:11Z",
      "signing_secret": "whsec_9f2c1b7e4d3a4f8b"
    }
  ],
  "next_cursor": "string",
  "total_count": 1
}
```

## Create a webhook endpoint revision

`POST /api/v1/webhook_endpoints/{endpoint_key}/revisions`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.write`

Creates a new revision of an endpoint from a name and a URL. If the key is new, this creates the endpoint and its signing secret. The secret is returned once, in this response, as `signing_secret`. Store it where your receiver can read it. It cannot be retrieved again.

Later revisions change the name or the URL only. The secret stays the same, so your receiver keeps working. The URL must be a public HTTPS address and is checked against the egress policy before the revision is created. The revision is created inactive. Activate it to put it into use.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |

Request body:

- `name` · string · required: A display name. Pipeline authors see it when they pick an endpoint.
- `url` · uri · required: An absolute public HTTPS URL. It is checked against the egress policy, which refuses private and internal addresses.

### Response

| Status | Body | Description |
| --- | --- | --- |
| 201 | WebhookEndpointRevision | The new revision, inactive. `signing_secret` is present only for the endpoint's first revision |
| 400 | ErrorResponse | The name is empty, or the URL fails the egress policy |
| 401 | ErrorResponse | You are not signed in |
| 403 | ErrorResponse | You cannot edit webhook endpoints |
| 501 | ErrorResponse | This deployment has no secrets key, so webhook endpoints are unavailable |

Response body (201):

- `id` · uuid · required
- `endpoint_key` · string · required
- `revision` · int64 · required: The revision number. Revisions count up from 1.
- `is_active` · boolean · required: Whether deliveries go to this revision.
- `name` · string · required: The display name.
- `url` · uri · required: Where deliveries go.
- `created_by_actor_id` · uuid · required: The membership or organization-key actor that created the revision.
- `created_at` · date-time · required
- `signing_secret` · string: Present only in the response that created the endpoint's first revision. Starts with `whsec_`. Your receiver uses it to verify the `WithHuman-Signature` header.

### Example

```bash
curl -X POST "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}/revisions" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Fraud check",
  "url": "https://fraud.example.com/approvals"
}'
```

201 response

```json
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "endpoint_key": "string",
  "revision": 1,
  "is_active": true,
  "name": "Fraud check",
  "url": "https://fraud.example.com/approvals",
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "signing_secret": "whsec_9f2c1b7e4d3a4f8b"
}
```

## Retrieve a webhook endpoint revision

`GET /api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.read`

Returns one revision of an endpoint, active or not. The signing secret is never included.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |
| `revision` | path | int64 | yes | The revision number. Revisions count up from 1. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 200 | WebhookEndpointRevision | The revision |
| 400 | ErrorResponse | `revision` is not a positive integer |
| 401 | ErrorResponse | You are not signed in |
| 403 | ErrorResponse | You cannot read webhook endpoints |
| 404 | ErrorResponse | No such revision |

Response body (200):

- `id` · uuid · required
- `endpoint_key` · string · required
- `revision` · int64 · required: The revision number. Revisions count up from 1.
- `is_active` · boolean · required: Whether deliveries go to this revision.
- `name` · string · required: The display name.
- `url` · uri · required: Where deliveries go.
- `created_by_actor_id` · uuid · required: The membership or organization-key actor that created the revision.
- `created_at` · date-time · required
- `signing_secret` · string: Present only in the response that created the endpoint's first revision. Starts with `whsec_`. Your receiver uses it to verify the `WithHuman-Signature` header.

### Example

```bash
curl -X GET "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
```

200 response

```json
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "endpoint_key": "string",
  "revision": 1,
  "is_active": true,
  "name": "Fraud check",
  "url": "https://fraud.example.com/approvals",
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "signing_secret": "whsec_9f2c1b7e4d3a4f8b"
}
```

## Activate a webhook endpoint revision

`POST /api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}/activate`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.write`

Makes a revision the active one. Deliveries go to its URL from the next request on. A request that is paused on a webhook block and resumes later delivers to whichever revision is active at that moment. Activating an older revision is how you roll back.

Pass the revision you expect to be active in `If-Match`, quoted, as returned in `ETag`. Pass `"0"` if no revision is active. If the active revision changed in the meantime, the call fails with 412 and nothing changes.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |
| `revision` | path | int64 | yes | The revision number. Revisions count up from 1. |
| `If-Match` | header | string | yes | The revision you expect to be active, quoted, for example `"2"`. Take it from the `ETag` of your last read. If the active revision changed in the meantime, the call fails with 412 and nothing changes. Escalation paths and webhook endpoints accept `"0"` when no revision is active. Pipelines always have an active revision. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 200 | WebhookEndpointRevision | The revision, now active |
| 400 | ErrorResponse | `revision` or `If-Match` is malformed |
| 401 | ErrorResponse | You are not signed in |
| 403 | ErrorResponse | You cannot edit webhook endpoints |
| 404 | ErrorResponse | No such revision |
| 412 | ErrorResponse | `If-Match` does not match the active revision |
| 428 | ErrorResponse | The `If-Match` header is missing |
| 501 | ErrorResponse | This deployment has no secrets key, so webhook endpoints are unavailable |

Response body (200):

- `id` · uuid · required
- `endpoint_key` · string · required
- `revision` · int64 · required: The revision number. Revisions count up from 1.
- `is_active` · boolean · required: Whether deliveries go to this revision.
- `name` · string · required: The display name.
- `url` · uri · required: Where deliveries go.
- `created_by_actor_id` · uuid · required: The membership or organization-key actor that created the revision.
- `created_at` · date-time · required
- `signing_secret` · string: Present only in the response that created the endpoint's first revision. Starts with `whsec_`. Your receiver uses it to verify the `WithHuman-Signature` header.

### Example

```bash
curl -X POST "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}/activate" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "If-Match: "2""
```

200 response

```json
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "endpoint_key": "string",
  "revision": 1,
  "is_active": true,
  "name": "Fraud check",
  "url": "https://fraud.example.com/approvals",
  "created_by_actor_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "created_at": "2026-09-08T12:02:11Z",
  "signing_secret": "whsec_9f2c1b7e4d3a4f8b"
}
```

## Test a webhook endpoint revision

`POST /api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}/test`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.write`

Sends one signed test delivery to the revision's URL and reports what came back. The payload has type `webhook_endpoint.test` and is flagged as a preview, so your receiver can tell it from a real request. It is signed with the endpoint's secret, so the test also checks your signature verification. The call waits up to 10 seconds for an answer.

You can test a revision before activating it. Each test is recorded in the audit log.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |
| `revision` | path | int64 | yes | The revision number. Revisions count up from 1. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 200 | object | What the endpoint answered |
| 400 | ErrorResponse | `revision` is not a positive integer |
| 403 | ErrorResponse | You cannot edit webhook endpoints |
| 404 | ErrorResponse | No such revision |
| 501 | ErrorResponse | This deployment has no secrets key, so webhook endpoints are unavailable |

Response body (200):

- `test` · WebhookEndpointTest · required: What one test delivery produced. problem is absent when the endpoint answered with a valid outcome.
  - `status_code` · integer · required: The HTTP status the endpoint returned.
  - `duration_ms` · int64 · required: How long the call took, in milliseconds.
  - `outcome` · enum: The outcome the endpoint answered with, when its answer was valid. One of `next`, `human`, `approve`, `deny`.
  - `reason` · string: The reason the endpoint gave, if any.
  - `problem` · enum: What went wrong, if anything. `blocked`: the URL fails the egress policy. `response_too_large`: the body was over 64 KiB. `timeout`: no answer within 10 seconds. `unreachable`: the connection failed. `status`: the endpoint answered with a status outside 2xx. `invalid_answer`: the body was not a valid outcome document. One of `blocked`, `response_too_large`, `timeout`, `unreachable`, `status`, `invalid_answer`.
  - `message` · string: Details about the problem, in plain words.

### Example

```bash
curl -X POST "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}/revisions/{revision}/test" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
```

200 response

```json
{
  "test": {
    "status_code": 1,
    "duration_ms": 1,
    "outcome": "next",
    "reason": "string",
    "problem": "blocked",
    "message": "string"
  }
}
```

## List where a webhook endpoint is used

`GET /api/v1/webhook_endpoints/{endpoint_key}/uses`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.read`

Returns the active pipeline revisions that post to this endpoint, one entry per webhook block. While this list is not empty, the endpoint cannot be archived.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 200 | object | The active uses. Empty when the endpoint can be archived |
| 400 | ErrorResponse | `endpoint_key` is malformed |
| 401 | ErrorResponse | You are not signed in |
| 403 | ErrorResponse | You cannot read webhook endpoints |

Response body (200):

- `uses` · array<PipelineUse> · required
  - `pipeline_id` · uuid · required
  - `scope` · enum · required: The pipeline's scope. One of `organization`, `agent`.
  - `agent_slug` · string: The agent's slug. Present for the `agent` scope only.
  - `revision` · int64 · required: The active revision number.
  - `block_key` · string: The block that uses the object. Absent when the revision names an escalation path as its default.

### Example

```bash
curl -X GET "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}/uses" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
```

200 response

```json
{
  "uses": [
    {
      "pipeline_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
      "scope": "organization",
      "agent_slug": "string",
      "revision": 1,
      "block_key": "string"
    }
  ]
}
```

## Archive a webhook endpoint

`DELETE /api/v1/webhook_endpoints/{endpoint_key}/active`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.write`

Deactivates the endpoint's current revision, so the endpoint has no active revision. Its history and its signing secret are kept, and you can activate a revision again later.

Archiving is refused while an active pipeline revision posts to the endpoint. Activate pipeline revisions without that block first. While the endpoint is archived, no pipeline revision that posts to it can be activated. A request that resumes onto it goes to a person, with the block error `endpoint_unavailable`.

Pass the active revision in `If-Match`, quoted, as returned in `ETag`.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |
| `If-Match` | header | string | yes | The revision you expect to be active, quoted, for example `"2"`. Take it from the `ETag` of your last read. If the active revision changed in the meantime, the call fails with 412 and nothing changes. Escalation paths and webhook endpoints accept `"0"` when no revision is active. Pipelines always have an active revision. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 204 |  | The endpoint is archived |
| 400 | ErrorResponse | `If-Match` is malformed or `"0"` |
| 401 | ErrorResponse | You are not signed in |
| 403 | ErrorResponse | You cannot edit webhook endpoints |
| 409 | ErrorResponse | An active pipeline revision posts to this endpoint. `error.code` is `webhook_endpoint_in_use`, and `error.details.uses` lists the revisions |
| 412 | ErrorResponse | `If-Match` does not match the active revision |
| 428 | ErrorResponse | The `If-Match` header is missing |

### Example

```bash
curl -X DELETE "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}/active" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY" \
  -H "If-Match: "2""
```

## Rotate the signing secret

`POST /api/v1/webhook_endpoints/{endpoint_key}/rotate_secret`

Auth: Reviewer session cookie (`withhuman_session`) or personal API key (`Authorization: Bearer $WITHHUMAN_API_KEY`)

Requires: `webhook.write`

Replaces the endpoint's signing secret with a new one. The old secret stops working at once, for every revision of the endpoint. The new secret is returned once, in this response. Update your receiver right away, or its signature checks will fail. Each rotation is recorded in the audit log.

### Request

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `endpoint_key` | path | string | yes | The endpoint's key. Lowercase letters, digits, dots, underscores and hyphens, up to 63 characters. You choose it when you create the first revision. |

### Response

| Status | Body | Description |
| --- | --- | --- |
| 200 | WebhookEndpointSecret | The new secret. It appears only in this response |
| 403 | ErrorResponse | You cannot edit webhook endpoints |
| 404 | ErrorResponse | No such endpoint |
| 501 | ErrorResponse | This deployment has no secrets key, so webhook endpoints are unavailable |

Response body (200):

- `endpoint_key` · string · required
- `signing_secret` · string · required: The new secret. It appears only in this response. Starts with `whsec_`. Your receiver uses it to verify the `WithHuman-Signature` header.
- `secret_rotated_at` · date-time · required: When the secret was rotated.

### Example

```bash
curl -X POST "$WITHHUMAN_URL/api/v1/webhook_endpoints/{endpoint_key}/rotate_secret" \
  -H "Authorization: Bearer $WITHHUMAN_API_KEY"
```

200 response

```json
{
  "endpoint_key": "string",
  "signing_secret": "whsec_9f2c1b7e4d3a4f8b",
  "secret_rotated_at": "2026-09-08T12:02:11Z"
}
```
