---
title: "Rotate a webhook signing secret"
method: POST
path: "/v1/webhooks/{id}/secret"
tags: ["Webhooks"]
---

# Rotate a webhook signing secret

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

## Request

- Base URL: `https://api.kawaa.com`
- URL: `https://api.kawaa.com/v1/webhooks/{id}/secret`
- Auth: one of:
  - API key in header `X-Api-Key`
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.kawaa.com/oauth/authorize`, token URL `https://api.kawaa.com/oauth/token`, scopes: `webhooks:write`)

Replace the endpoint's signing secret without deleting or reconfiguring the endpoint. A successful call returns the replacement exactly once; deliveries that load the endpoint afterward use it, while a delivery already in flight may still carry a signature made with the previous value. Store the returned value before leaving the response; no read operation can recover it.

**The previous secret stops working at once.** There is no overlap: every later delivery is signed with the replacement only. Until your receiver has it, it refuses those deliveries, and a 401 or any other 4xx answer is final and not retried, so events sent in that gap are not delivered on their own. If you can, have your receiver accept a signature made with either secret while the replacement rolls out, then drop the old one. Re-send what failed in the gap with `POST /v1/webhooks/{id}/logs/{logId}/retry`; a re-send is signed with the current secret.

With a dashboard session this needs a sign-in within the last 15 minutes, as changing `url` or `events` does; an older session answers `403 REAUTHENTICATION_REQUIRED`. An API key or a connected app is not held to this. Each rotation is recorded in the audit log as `settings.webhook_secret_regenerated`, without either secret.

## Path parameters

- `id` string, required

## Response `200`

The secret was replaced. Subsequent deliveries are signed with this value, except that a delivery already in flight may have loaded the previous secret before rotation.

- object
  - `success` unknown, required
  - `data` object, required
    - `secret` string, required: The replacement signing secret, returned by this successful rotation only. It is now the value used for `X-Kawaa-Signature-V2` and the legacy `X-Kawaa-Signature`.
    - `message` string, required

## Other responses

- `401`: No credential, or one that is not accepted. `code` distinguishes `API_KEY_SUSPENDED` (subscription ended; restartable) from `API_KEY_REVOKED`, `SESSION_EXPIRED` and `OAUTH_TOKEN_EXPIRED` (a connected app's one-hour access token lapsed: refresh it and retry, or sign in again if no refresh token was issued or the refresh is refused).
- `403`: Refused, and **the code says which of three problems it is**, and they need different remedies: - `INSUFFICIENT_SCOPE`: the credential is valid but not scoped for this route. With an API key, issue one that carries the scope named in the message. With an OAuth access token, connect the app again asking for that scope as well as the ones it has, unless the message says a connected app can never be given it; then use an API key with every scope the message names, or have the account owner do it in the dashboard. - `PLAN_UPGRADE_REQUIRED`: the account's plan does not include the feature. **No key of any scope reaches it**, so issuing another is wasted work; the remedy is a plan change. Returned by the verification routes for `options.enrich`, and by the finder, the monitors, the audit log, auto-clean and the white-label routes. - `PLAN_LIMIT_EXCEEDED`: the plan includes the feature and the account is at its ceiling (webhook endpoints, team seats, integrations, auto-clean lists). Delete one or upgrade. - `FORBIDDEN`: refused for a reason none of the above covers. `ErrorCode` is an open set, so branch on the code with a default rather than exhaustively.
- `404`: No such resource on this account.
- `429`: Too many requests. When the server knows how long to wait, `Retry-After` says so and `error.retry_after_seconds` carries the same number; the `X-RateLimit-*` headers say where you stand.
- `500`: The rotation outcome is **indeterminate**: the write may have committed even though its response was lost. Retrying is safe for endpoint configuration but rotates again. Retry this same operation until one call succeeds, then keep only the secret from that successful response; it supersedes every earlier value.
- `default`: Any status this operation does not list. Kawaa's own errors carry the error envelope. The gateway in front of the API can answer any operation with its own bare body (`GatewayError`): a `503` after about 29 seconds, when the request may still have run and been charged, a `413` for a body over about 6 MB, or a `400` for a path with malformed percent-encoding.

---

[API](https://skmtc.dev/kawaa/apis/kawaa-api.md) · [All operations](https://skmtc.dev/kawaa/apis/kawaa-api/llms.txt) · [OpenAPI document](https://skmtc.dev/kawaa/apis/kawaa-api/revisions/d27c9c33f6ba?raw)
