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.
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
Path Parameters
The webhook endpoint's id.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/integrations/webhooks/string/roll-secret"{ "data": { "previousValidUntil": "2026-08-25T09:14:22Z", "secret": "whsec_8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f" }}Send a delivery again now
Re-sends immediately instead of waiting for the next backoff step. The event id is unchanged, so a correctly written consumer deduplicates it.
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.