Rotate Webhook Secret
curl --request POST \
--url https://api.signa.so/v1/webhooks/{id}/rotate-secret \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"force": true,
"reason": "<string>"
}
'import requests
url = "https://api.signa.so/v1/webhooks/{id}/rotate-secret"
payload = {
"force": True,
"reason": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({force: true, reason: '<string>'})
};
fetch('https://api.signa.so/v1/webhooks/{id}/rotate-secret', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.signa.so/v1/webhooks/{id}/rotate-secret",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'force' => true,
'reason' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.signa.so/v1/webhooks/{id}/rotate-secret"
payload := strings.NewReader("{\n \"force\": true,\n \"reason\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.signa.so/v1/webhooks/{id}/rotate-secret")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"force\": true,\n \"reason\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.signa.so/v1/webhooks/{id}/rotate-secret")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"force\": true,\n \"reason\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "whk_2mR8vNkT",
"object": "webhook_endpoint",
"url": "https://hooks.auroradigital.example.com/signa",
"description": "Production alerts for Aurora Digital",
"enabled_events": ["alert.created"],
"status": "active",
"secret_version": 2,
"secret": "whsec_1a2b3c4d5e6f7081920a1b2c3d4e5f60",
"consecutive_failures": 0,
"last_success_at": "2026-07-05T09:12:31.000Z",
"last_failure_at": null,
"metadata": {},
"created_at": "2026-07-06T08:00:00.000Z",
"updated_at": "2026-07-06T09:00:00.000Z",
"request_id": "req_3vXq7RmT"
}
Webhooks
Rotate Webhook Secret
Roll the signing secret with a 24-hour overlap window
POST
/
v1
/
webhooks
/
{id}
/
rotate-secret
Rotate Webhook Secret
curl --request POST \
--url https://api.signa.so/v1/webhooks/{id}/rotate-secret \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"force": true,
"reason": "<string>"
}
'import requests
url = "https://api.signa.so/v1/webhooks/{id}/rotate-secret"
payload = {
"force": True,
"reason": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({force: true, reason: '<string>'})
};
fetch('https://api.signa.so/v1/webhooks/{id}/rotate-secret', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.signa.so/v1/webhooks/{id}/rotate-secret",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'force' => true,
'reason' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.signa.so/v1/webhooks/{id}/rotate-secret"
payload := strings.NewReader("{\n \"force\": true,\n \"reason\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.signa.so/v1/webhooks/{id}/rotate-secret")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"force\": true,\n \"reason\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.signa.so/v1/webhooks/{id}/rotate-secret")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"force\": true,\n \"reason\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "whk_2mR8vNkT",
"object": "webhook_endpoint",
"url": "https://hooks.auroradigital.example.com/signa",
"description": "Production alerts for Aurora Digital",
"enabled_events": ["alert.created"],
"status": "active",
"secret_version": 2,
"secret": "whsec_1a2b3c4d5e6f7081920a1b2c3d4e5f60",
"consecutive_failures": 0,
"last_success_at": "2026-07-05T09:12:31.000Z",
"last_failure_at": null,
"metadata": {},
"created_at": "2026-07-06T08:00:00.000Z",
"updated_at": "2026-07-06T09:00:00.000Z",
"request_id": "req_3vXq7RmT"
}
Overview
Rotates the signing secret. The new secret is returned once in the response. The previous secret remains valid for 24 hours, during which every delivery is signed with both:webhook-signature: v1,<new> v1,<old> (space-separated, per the Standard Webhooks spec). Update
your verifier to the new secret any time within the window; no deliveries are missed. A second
rotation attempt while that window is still open returns 409 unless you pass force: true.
Requires the portfolios:manage scope.
Path Parameters
string
required
Endpoint ID (
whk_*).Query Parameters
boolean
default:"false"
Convenience alias for
force in the body. ?force=true is equivalent to {"force": true} in
the JSON body.Body Parameters
boolean
default:"false"
Emergency use only. When
true, skips the 24-hour overlap window and immediately invalidates
the previous secret. Any receiver still using the previous secret fails signature
verification on the next delivery. Without force, rotating while the previous secret is
still within its 24-hour window returns 409.string
Optional human-readable reason (max 500 chars), recorded on the audit event when
force: true. Ignored otherwise.Response
AWebhook with the new secret and bumped secret_version.
Errors
| Status | type | When |
|---|---|---|
| 404 | not_found | Endpoint doesn’t exist or belongs to another org |
| 409 | conflict | The previous secret’s 24-hour overlap window is still active and force wasn’t true |
Code Examples
curl -X POST "https://api.signa.so/v1/webhooks/whk_2mR8vNkT/rotate-secret" \
-H "Authorization: Bearer sig_YOUR_KEY" \
-H "Idempotency-Key: rotate-whk-2mR8vNkT-2026-06-12"
curl -X POST "https://api.signa.so/v1/webhooks/whk_2mR8vNkT/rotate-secret" \
-H "Authorization: Bearer sig_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: force-rotate-whk-2mR8vNkT-2026-06-12" \
-d '{"force": true, "reason": "Secret leaked in a client-side log"}'
import { Signa } from "@signa-so/sdk";
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });
const rotated = await signa.webhooks.rotateSecret("whk_2mR8vNkT");
// Store rotated.secret in your secrets manager: it is not returned again.
console.log(rotated.secret);
{
"id": "whk_2mR8vNkT",
"object": "webhook_endpoint",
"url": "https://hooks.auroradigital.example.com/signa",
"description": "Production alerts for Aurora Digital",
"enabled_events": ["alert.created"],
"status": "active",
"secret_version": 2,
"secret": "whsec_1a2b3c4d5e6f7081920a1b2c3d4e5f60",
"consecutive_failures": 0,
"last_success_at": "2026-07-05T09:12:31.000Z",
"last_failure_at": null,
"metadata": {},
"created_at": "2026-07-06T08:00:00.000Z",
"updated_at": "2026-07-06T09:00:00.000Z",
"request_id": "req_3vXq7RmT"
}
Related Endpoints
- Retrieve Webhook - check
secret_versionon an endpoint - Webhooks guide - signature verification