> ## Documentation Index
> Fetch the complete documentation index at: https://docs.offthehook.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /v1/subscriptions/:id/secrets/rotate

> Rotate a subscription's webhook signing secret. During the grace period, deliveries are signed with both old and new secrets so you can deploy without downtime.

This endpoint generates a new webhook signing secret for a subscription. To avoid dropping deliveries during a key rotation, you can specify a grace period during which the service signs each delivery with **both** the old and the new secret. Your endpoint can then validate against either, giving you time to deploy the new secret without downtime.

## Request

```
POST /v1/subscriptions/:id/secrets/rotate
```

### Headers

| Header            | Required | Description                                                                  |
| ----------------- | -------- | ---------------------------------------------------------------------------- |
| `Authorization`   | Yes      | `Bearer oth_YOUR_API_KEY`                                                    |
| `Content-Type`    | Yes      | `application/json`                                                           |
| `Idempotency-Key` | No       | A unique key to safely retry this request without causing a double rotation. |

### Path parameters

<ParamField path="id" type="string" required>
  The subscription ID (e.g. `sub_2QkP9aB7xN...`).
</ParamField>

### Body

<ParamField body="gracePeriodSeconds" type="integer">
  How long, in seconds, the old secret remains valid alongside the new one. Set to `0` for an immediate cutover — the old secret becomes invalid as soon as the request completes. Defaults to `0`.
</ParamField>

```bash theme={null}
curl -X POST https://api.offthehook.dev/v1/subscriptions/sub_2QkP9aB7xN.../secrets/rotate \
  -H "Authorization: Bearer oth_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rotate-$(date +%s)" \
  -d '{ "gracePeriodSeconds": 3600 }'
```

## Response

**200 OK** — the newly generated secret.

```json theme={null}
{ "secret": "whsec_NEW_SECRET..." }
```

<Warning>
  The new secret is returned **only in this response**. Store it immediately before making any other API calls. After the grace period ends, the old secret is invalid and cannot be recovered.
</Warning>

### During the grace period

While the grace period is active, the `webhook-signature` delivery header contains space-separated `v1,` signatures for both secrets:

```
webhook-signature: v1,<base64-hmac-new-secret> v1,<base64-hmac-old-secret>
```

The [Svix verification library](https://github.com/svix/svix-webhooks) handles multi-signature validation automatically — you do not need to change your verification code during the transition.

### Idempotency

Use the `Idempotency-Key` header to safely retry this request if you receive a network error or timeout. If the server has already processed a request with the same key, it returns the cached response without generating a new secret. Sending a conflicting body with the same key returns `idempotency_conflict`.

## Errors

| Code                    | HTTP | Description                                                                                    |
| ----------------------- | ---- | ---------------------------------------------------------------------------------------------- |
| `not_found`             | 404  | The subscription ID does not exist or belongs to a different API key.                          |
| `idempotency_in_flight` | 409  | A request with this `Idempotency-Key` is currently being processed. Retry after a short delay. |
| `idempotency_conflict`  | 409  | A request with this `Idempotency-Key` was already completed with a different request body.     |
| `unauthorized`          | 401  | The `Authorization` header is missing or the key is invalid.                                   |
