---
title: "Rotate a Webhook's secret"
method: POST
path: "/webhooks/{webhook-id}/rotate-secret"
tags: ["webhooks"]
---

# Rotate a Webhook's secret

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

## Request

- Base URL: `https://api.withpersona.com/api/v1`
- URL: `https://api.withpersona.com/api/v1/webhooks/{webhook-id}/rotate-secret`
- Auth: HTTP bearer

Rotates a webhook's secret by creating a new secret.

The old secret stays active for the number of seconds you pass in `meta.expires-in-seconds`. If you omit it, it defaults to `0`: the old secret expires immediately and there is no overlap, so deliveries signed only with the new secret start right away. To rotate without downtime, set `expires-in-seconds` long enough to cover deploying the new secret to your endpoint.

## Signatures during rotation

While both secrets are active, Persona signs every webhook event with each of them. The `Persona-Signature` header then contains one `t=<unix_timestamp>,v1=<signature>` set per active secret, separated by a space:

```text
t=1764686826,v1=<signature_from_one_secret> t=1764686826,v1=<signature_from_the_other_secret>
```

The order of the sets is not guaranteed. Your handler must check every `v1` signature in the header and accept the request if **any** of them matches. Once the old secret expires, the header contains a single set again.

## Rotating without downtime

1. Update your handler to check every `v1` signature in the `Persona-Signature` header, as described in [Checking signatures](https://docs.withpersona.com/webhooks-best-practices#checking-signatures).
2. Call this endpoint with an `expires-in-seconds` long enough to cover your deployment.
3. Deploy the new secret to your endpoint.
4. Let the old secret expire.

A webhook can have only one expiring secret at a time. Calling this endpoint again while the old secret is still active does not create another secret. Instead, it moves the old secret's expiration to `expires-in-seconds` from now, which can only make it sooner (pass `0` to expire it immediately).

For more details, see [Rotating secrets safely](https://docs.withpersona.com/webhooks-best-practices#rotating-secrets-safely) and [Quickstart: Webhooks](https://docs.withpersona.com/quickstart-webhooks).

## Path parameters

- `webhook-id` string, required

## Query parameters

- `include` string
- `fields` object

## Headers

- `Key-Inflection` 'camel' | 'kebab' | 'snake'
- `Idempotency-Key` string
- `Persona-Version` '2026-09-29' | '2025-12-08' | '2025-10-27' | '2023-01-05' | '2022-09-01' | '2021-08-18' | '2021-07-05' | '2021-02-21' | '2020-05-18': Server API version. More info on versioning can be found [here](https://docs.withpersona.com/versioning).

## Request body

- object
  - `meta` WebhooksWebhookIdRotateSecretPostRequestBodyContentApplicationJsonSchemaMeta
    - `expires-in-seconds` integer: Number of seconds the current secret stays active after rotation. During this window Persona signs each webhook event with both the old and new secrets. Defaults to `0`, which expires the old secret immediately. Set it long enough to deploy the new secret to your endpoint. If the webhook already has an expiring secret, this instead sets that secret's expiration to this many seconds from now. It can only move the expiration sooner.

## Response `200`

This endpoint returns a Webhook object.

- WebhooksRotateAWebhookSecretResponse200
  - `data` WebhookWithSecret, required: A Webhook object
    - `type` 'webhook'
    - `id` string
    - `attributes` WebhookWithSecretAttributes
      - `status` string
      - `url` string
      - `name` string, nullable
      - `description` string, nullable
      - `secret` string: secret is deprecated in favor of secrets
      - `secrets` WebhookWithSecretAttributesSecretsItems[]
        - `value` string
        - `expires-at` string, date-time, nullable
      - `api-version` '2026-09-29' | '2025-12-08' | '2025-10-27' | '2023-01-05' | '2022-09-01' | '2021-08-18' | '2021-07-05' | '2021-02-21' | '2020-05-18': Server API version. More info on versioning can be found [here](https://docs.withpersona.com/versioning).
      - `api-key-inflection` string
      - `api-attributes-blocklist` string[]
      - `file-access-token-expires-in` integer
      - `enabled-events` string[]
      - `payload-filter` WebhookWithSecretAttributesPayloadFilter
      - `included-allowlist` union: Controls which related objects appear in the `included` array of webhook payloads. The default is `include_none`. When `state` is `custom`, `event-types` configures which related objects to include per event type: event types you are subscribed to (via `enabled-events`) but do not list are excluded, and event types you list but are not subscribed to are dropped on save. A `custom` config whose event types are all `["*"]` is equivalent to, and is normalized to, `include_all`.
        - IncludedAllowlist0
          - `state` 'include_all', required: Include all related objects for every subscribed event type.
        - IncludedAllowlist1
          - `state` 'include_none', required: Include no related objects (the default).
        - IncludedAllowlist2
          - `state` 'custom', required: Configure included related objects per event type via `event-types`.
          - `event-types` IncludedAllowlistOneOf2EventTypesItems[], required: Per-event-type include configuration. At most one entry per event type.
            - `event-type` string, required: Event type identifier (e.g. `inquiry`, `account`).
            - `relationships` string[], required: Related object names to include for this event type, or `["*"]` to include all related objects. `"*"` cannot be combined with named relationships.
      - `relationship-allowlist` union: Controls whether relationships are serialized in webhook payload objects. The default is `include_all`. The object shape leaves room for future granular relationship configuration.
        - RelationshipAllowlist0
          - `state` 'include_all', required: Include relationship linkages in webhook payload objects.
        - RelationshipAllowlist1
          - `state` 'include_none', required: Omit relationship linkages from webhook payload objects.
      - `created-at` string, date-time
  - `included` unknown[]: Excluded from the response by default. Will be an empty array when the `include` query parameter in the request was explicitly set to empty.
    - unknown

## Other responses

- `400`: The request was unacceptable, often due to invalid parameters.
- `401`: An invalid API key was provided.
- `403`: The given API key doesn’t have permissions to perform the request or a quota has been exceeded.
- `404`: The requested resource doesn’t exist.
- `409`: The request conflicts with another request, often due to attempting to create a duplicate resource.
- `422`: The request modifies the resource in an unacceptable way, often due to an invalid action or parameter.
- `429`: Your organization’s rate limit has been exceeded. We recommend an exponential backoff on requests.

---

[API](https://skmtc.dev/withpersona/apis/withpersona-4.md) - [All operations](https://skmtc.dev/withpersona/apis/withpersona-4/llms.txt) - [OpenAPI document](https://skmtc.dev/withpersona/apis/withpersona-4/revisions/2eddd7dae374?raw)
