> ## 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.

# Rotate Webhook Signing Secrets

> Rotate a subscription's signing secret without downtime. Off the Hook signs with both old and new secrets during a configurable grace period.

Every subscription has a signing secret used to compute the HMAC-SHA256 signature on each delivery. You should rotate this secret periodically or immediately if you suspect it has been compromised. Off the Hook supports zero-downtime rotation by signing deliveries with both the old and new secrets during a configurable grace period, giving you time to deploy the updated secret before the old one expires.

<Warning>
  The new secret is returned only in the rotation response. Store it immediately — there is no way to retrieve it again after the request completes.
</Warning>

## Rotate the secret

Send a POST request to the rotate endpoint with the desired grace period in seconds.

```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 }'
```

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

The `gracePeriodSeconds` field controls how long the old secret remains valid alongside the new one. Set it to `0` for an immediate cutover with no grace period.

<Tip>
  Use a grace period of at least 3600 seconds (1 hour) to give yourself enough time to update and deploy your verification code across all services before the old secret expires.
</Tip>

## How the grace period works

During the grace period, Off the Hook signs each delivery with **both** secrets. The `webhook-signature` header contains two space-separated `v1,` values:

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

The Svix verification library automatically accepts any valid signature in the header, so your existing verification code continues to work without modification while you roll out the new secret.

Once the grace period expires, deliveries are signed only with the new secret. Any verification using the old secret will fail at that point.

## Idempotency

Include an `Idempotency-Key` header on the rotation request to safely retry it without triggering a double rotation. If a request with the same key is already in flight, the API returns `idempotency_in_flight`. If a completed request with the same key is replayed, you receive the original response.

```bash theme={null}
-H "Idempotency-Key: rotate-$(date +%s)"
```

Use a unique, stable key per intended rotation — for example, a timestamp or a UUID generated before you send the request.

## Rotation workflow

<Steps>
  <Step title="Rotate the secret">
    Call the rotate endpoint and save the new `whsec_...` value returned in the response.
  </Step>

  <Step title="Deploy the new secret">
    Update your environment variables or secrets manager with the new value and deploy your service. Your verification code does not need to change — the Svix library handles both signatures automatically during the grace period.
  </Step>

  <Step title="Confirm the grace period has elapsed">
    After the grace period expires, the old secret is no longer valid. Verify that your service is successfully processing deliveries signed only with the new secret.
  </Step>
</Steps>
