Regenerate a workflow hook secret
Replace the signing secret of a send_webhook hook and return the new one. Use it to rotate the secret on a schedule, or to recover after losing the secret from the create response.
This is a bodyless POST. The response carries the new secret in plain text. No later read returns the secret, so store it from this response.
The old secret stops validating immediately. Deliveries after this call carry an X-Plane-Signature computed with the new secret, so update your receiver as soon as you have it.
Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.
project_id:requiredstringThe project the workflow belongs to. Accepts the project UUID or its bare identifier, for example ENG.
workflow_id:requiredstring (uuid)The workflow the transition belongs to.
transition_id:requiredstring (uuid)The transition the hook is attached to.
pk:requiredstring (uuid)The id of the send_webhook hook whose secret you want to replace.
Body Parameters
None. The endpoint takes no request body and ignores anything you send.
Scopes
projects.workflows:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | The hook's handler_name is run_script. Only send_webhook hooks have a secret. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | Workflows aren't enabled on your plan. |
403 | forbidden | Your role or token scope can't edit this project's workflows. |
404 | not_found | No such hook, transition, workflow, project, or workspace. Also returned when the hook was deleted or the transition is in the catalog of a governed workspace. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | conflict | Not raised by the project hook routes. Listed for parity with the workspace hook routes. |
413 | payload_too_large | The request body is over the size limit. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X POST \
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/workflows/9e3f6a2c-7b14-4d8e-a5c1-2f60b8d4e719/state-transitions/5c8d2e71-4a9f-4b36-8e05-d17f3a6b9c42/hooks/3b7e9a15-6c2d-4f84-9b31-0e5d8c7a2f69/regenerate-webhook-secret/" \
-H "X-Api-Key: $PLANE_API_KEY"import requests
response = requests.post(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b"
"/workflows/9e3f6a2c-7b14-4d8e-a5c1-2f60b8d4e719"
"/state-transitions/5c8d2e71-4a9f-4b36-8e05-d17f3a6b9c42"
"/hooks/3b7e9a15-6c2d-4f84-9b31-0e5d8c7a2f69/regenerate-webhook-secret/",
headers={"X-Api-Key": "your-api-key"},
)
new_secret = response.json()["secret"]const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b" +
"/workflows/9e3f6a2c-7b14-4d8e-a5c1-2f60b8d4e719" +
"/state-transitions/5c8d2e71-4a9f-4b36-8e05-d17f3a6b9c42" +
"/hooks/3b7e9a15-6c2d-4f84-9b31-0e5d8c7a2f69/regenerate-webhook-secret/",
{
method: "POST",
headers: { "X-Api-Key": "your-api-key" },
},
);
const { secret } = await response.json();{
"secret": "plane_wh_8d2c6f1a9e4b47d3b0a5e7c19f3d6a28"
}{
"type": "invalid_request",
"code": "invalid_request",
"detail": "Only send_webhook hooks carry a webhook secret."
}Rotation has no grace period
Plane keeps one secret per hook. Between this call and the moment your receiver switches to the new secret, deliveries fail signature checks on your side. If your receiver can, accept both the old and the new secret for a short window while you roll out the change.

