# Rotate the signing secret

> Makes a new signing secret, shown this once.

`POST /v1/webhooks/{id}/rotate-secret`

Makes a new signing secret, shown this once. For 24 hours each delivery is signed with both, so you can switch without missing one.

Needs a key with the `webhooks:manage` scope. [Keys and scopes](https://usebluebot.com/docs/concepts/api-keys)

Send an `Idempotency-Key` so a retry never acts twice. [Idempotency](https://usebluebot.com/docs/concepts/idempotency)

## Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | The webhook’s id. Starts with `wh_` |

## Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | No | Any string of up to 255 characters, new for each request. A retry with the same key gets the first answer back for 24 hours, with `Idempotent-Replayed: true`. Up to 255 characters |

## Example

```bash
curl -X POST "https://usebluebot.com/api/v1/webhooks/wh_1Hq2LxW9cVbN3mKpRtYs0a/rotate-secret" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Idempotency-Key: 0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70"
```

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const webhook = await client.webhooks.rotateSecret('wh_1Hq2LxW9cVbN3mKpRtYs0a', { idempotencyKey: '0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70' });
console.log(webhook);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

webhook = client.webhooks.rotate_secret(
    "wh_1Hq2LxW9cVbN3mKpRtYs0a",
    idempotency_key="0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70",
)
print(webhook)
```

## Response

`200`: The webhook, with its new secret.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | A wh_ id. Starts with `wh_` |
| `object` | "webhook" | Yes |   |
| `url` | string | Yes |   |
| `description` | string or null | Yes |   |
| `events` | array of strings | Yes | The event types it receives. For a webhook made with `["*"]`, every type there is now. One of `message.queued`, `message.sent`, `message.delivered`, `message.read`, `message.failed`, `message.unconfirmed`, `message.expired`, `message.canceled`, `message.received`, `message.edited`, `message.unsent`, `reaction.added`, `reaction.removed`, `chat.created`, `contact.created`, `contact.stopped`, `contact.unstopped`, `sequence.completed`, `broadcast.completed`, `automation.matched`, `device.online`, `device.offline`, `device.check_failed` |
| `all_events` | boolean | Yes | True when it was made with `["*"]`: it also receives event types BlueBot adds later. |
| `api_version` | string | Yes | The payload version it was made with and keeps. |
| `status` | string | Yes | `active`; `failing` while deliveries fail (retried for about 3 days); `paused` by you, or after 72 hours with no successful answer. One of `active`, `failing`, `paused` |
| `paused_at` | timestamp or null | Yes | When it was paused. |
| `pause_reason` | string or null | Yes | Who paused it: `failing` (72 hours of failures), `customer` or `staff`. One of `failing`, `customer`, `staff` |
| `failing_since` | timestamp or null | Yes | The first failure since the last success. |
| `last_success_at` | timestamp or null | Yes | The last delivery answered with a 2xx. |
| `last_failure_at` | timestamp or null | Yes | The last delivery that failed. |
| `last_status` | integer or null | Yes | The HTTP status of the last answer. |
| `previous_secret_expires_at` | timestamp or null | Yes | After a rotation, when the old secret stops signing alongside the new one. |
| `created_at` | timestamp | Yes | When it was made. |
| `secret` | string | Yes | The signing secret, `whsec_` and base64. Verify `webhook-signature` with it. Shown when the webhook is made and when the secret is rotated. |

The answer, `200`:

```json
{
  "id": "wh_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "webhook",
  "url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
  "description": "New replies to my CRM",
  "events": [
    "message.received",
    "message.failed"
  ],
  "all_events": false,
  "api_version": "2026-11-13",
  "status": "active",
  "paused_at": null,
  "pause_reason": null,
  "failing_since": null,
  "last_success_at": "2026-11-13T19:44:13.000Z",
  "last_failure_at": null,
  "last_status": 200,
  "previous_secret_expires_at": "2026-11-14T19:41:00.000Z",
  "created_at": "2026-11-01T15:10:00.000Z",
  "secret": "whsec_Zx1q4V8nT2mL6cR0bW9kHs3dPfA7yJuE"
}
```

## Errors

Every refusal has the same envelope and a page of its own. These are the ones this operation can give.

| Code                                                        | Status | What it means                                                                                                                               |
| ----------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| [`unauthorized`](https://usebluebot.com/docs/errors/unauthorized)                 | 401    | The request carried no key, or a key BlueBot doesn’t know: mistyped, revoked, or a test key sent to the live API.                           |
| [`insufficient_scope`](https://usebluebot.com/docs/errors/insufficient_scope)     | 403    | The key doesn’t carry the scope the endpoint needs. Each endpoint’s reference names its scope.                                              |
| [`rate_limited`](https://usebluebot.com/docs/errors/rate_limited)                 | 429    | Too many requests. A key may make 120 requests a minute. The `RateLimit` header says how many are left, and `Retry-After` how long to wait. |
| [`internal`](https://usebluebot.com/docs/errors/internal)                         | 500    | Something went wrong on BlueBot’s side. It was reported. Send the request again; with an Idempotency-Key that is always safe.               |
| [`not_found`](https://usebluebot.com/docs/errors/not_found)                       | 404    | There is no such thing on this account. Ids are prefixed by kind, so a message id starts with `msg_`.                                       |
| [`idempotency_conflict`](https://usebluebot.com/docs/errors/idempotency_conflict) | 409    | The Idempotency-Key was used in the last 24 hours with a different request. A key stands for one request.                                   |
| [`conflict`](https://usebluebot.com/docs/errors/conflict)                         | 409    | The request doesn’t fit the thing’s current state, or another request with the same Idempotency-Key is still running.                       |

## Try it
