The organization-level signing secret (endpoints created before per-endpoint secrets)
🔴 Only endpoints created before per-endpoint secrets sign with this. Every newer endpoint signs with its own — read it with GET /v1/integrations/webhooks/{id}/secret, which also answers correctly for an older endpoint. Compare x-tone-signature against an HMAC-SHA256 of {timestamp}.{raw body} — the RAW body, before any JSON parsing. A live key or the dashboard only: a test key gets 403, because an endpoint receives live events too.
Requires the admin scope. A key with less gets 403 insufficient_scope.
Authorization: Bearer tone_live_… or tone_test_…. The prefix IS the environment: a test key reaches only the sandbox, and no request field bridges the two.
In: header
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/integrations/webhooks/signing-secret"{ "data": { "note": "Signs every delivery to this endpoint as x-tone-signature.", "secret": "whsec_3f1a7f6e2b4c4d1e9a8b5c6d7e8f9a0b" }}Rotate the signing secret (old one keeps verifying for 24h)
🔴 During the overlap x-tone-signature carries SEVERAL space-delimited signatures, and a verifier that treats the header as one string starts rejecting everything. Accept the delivery if ANY of them matches. Deploy the new secret within the 24 hours. One rotation at a time: a second roll while that window is open is a 409. A live key or the dashboard only: a test key gets 403, because an endpoint receives live events too.
Update a webhook endpoint
Change the URL, the subscribed events, or disable it. Disabling stops delivery without losing the endpoint, its secret or its history. A live key or the dashboard only: a test key gets 403, because an endpoint receives live events too.