> For a complete page index of the Captain API documentation, fetch https://docs.captain.dev/llms.txt?excludeSpec=true

# Rotate Signing Secret

POST https://api.captain.dev/v2/webhooks/endpoints/{endpoint_id}/rotate-secret

Create a new signing secret for an endpoint. The new secret is returned only in this response.

For the next 24 hours every request carries two signatures, one with the old secret and one with the new, so the receiver keeps verifying whichever secret it holds. Deploy the new secret within that window.

Reference: https://docs.captain.dev/reference/webhooks/endpoints/rotate-secret

## Request

### Path parameters

- `endpoint_id` (string, required) — The endpoint's id (`whe_...`).

## Response

### 200

Successful Response

- `endpoint_id` (string, required)
- `secret` (string, required) — The new signing secret (`whsec_...`). Returned only in this response.
- `previous_secret_valid_for_hours` (integer, required) — How long requests stay signed with the old secret as well (24).

## Errors

### 401 Unauthorized Error

Missing or invalid API key.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 403 Forbidden Error

The connection is read-only and cannot change webhooks (MCP connections without write access).

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 404 Not Found Error

`Webhook endpoint not found.` The id does not exist, or belongs to another organization.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 429 Too Many Requests Error

`Too many webhook management requests. Try again shortly.` Retry after the `Retry-After` header.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 503 Service Unavailable Error

Webhooks are temporarily unavailable. Retry shortly.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

## Examples

**Response**

```json
{
  "endpoint_id": "whe_3f9a1c7e5b2d4a6c8e0f1a2b",
  "secret": "whsec_Qm7xN2cV9bL4tR8wK1pS6yD3hF0jG5aZ2eU9iO4nT7s=",
  "previous_secret_valid_for_hours": 24
}
```

**SDK Code**

```python Python
import os

import requests

BASE_URL = "https://api.captain.dev"
API_KEY = "your_api_key"
SECRET_FILE = "captain-webhook.env"
ENDPOINT_ID = "whe_3f9a1c7e5b2d4a6c8e0f1a2b"


def save_webhook_secret(secret):
    """Write the secret to an env file only this user can read. The receiver loads it as CAPTAIN_WEBHOOK_SECRET."""
    fd = os.open(SECRET_FILE, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    os.fchmod(fd, 0o600)  # also tightens a file that already existed
    with os.fdopen(fd, "w") as secret_file:
        secret_file.write(f"CAPTAIN_WEBHOOK_SECRET={secret}\n")


response = requests.post(
    f"{BASE_URL}/v2/webhooks/endpoints/{ENDPOINT_ID}/rotate-secret",
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=30.0,
)
response.raise_for_status()
data = response.json()
save_webhook_secret(data["secret"])
print(f"Saved the new signing secret to {SECRET_FILE}")
print(f"The old secret keeps working for {data['previous_secret_valid_for_hours']} hours")

```

```typescript TypeScript
import { closeSync, fchmodSync, openSync, writeSync } from "node:fs";

const BASE_URL = "https://api.captain.dev";
const API_KEY = "your_api_key";
// The receiver loads this file and reads CAPTAIN_WEBHOOK_SECRET. Only this user can read it.
const SECRET_FILE = "captain-webhook.env";
const ENDPOINT_ID = "whe_3f9a1c7e5b2d4a6c8e0f1a2b";

function saveWebhookSecret(secret: string) {
  const fd = openSync(SECRET_FILE, "w", 0o600);
  fchmodSync(fd, 0o600); // also tightens a file that already existed
  writeSync(fd, `CAPTAIN_WEBHOOK_SECRET=${secret}\n`);
  closeSync(fd);
}

const response = await fetch(`${BASE_URL}/v2/webhooks/endpoints/${ENDPOINT_ID}/rotate-secret`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${API_KEY}`,
  },
});
if (!response.ok) throw new Error(`Request failed: ${response.status} ${await response.text()}`);
const data = await response.json();
saveWebhookSecret(data.secret);
console.log(`Saved the new signing secret to ${SECRET_FILE}`);
console.log(`The old secret keeps working for ${data.previous_secret_valid_for_hours} hours`);

```