Rotating secrets

Roll a signing secret with zero dropped deliveries.

POST /webhooks/{id}/rotate-secret returns a new plaintext secret and retires the old one. To avoid a verification cliff while you redeploy, the retired secret keeps co-signing alongside the new one for a grace window (WEBHOOK_SECRET_GRACE_HOURS, default 24h, capped at 72h). During the window the Drazill-Webhook-Signature header contains both candidates, so consumers on either secret verify.

Roll like this:

1

Rotate

Call POST /webhooks/{id}/rotate-secret and store the new secret. The plaintext is returned once.

2

Deploy

Deploy the new secret to your receiver. Either secret verifies now, so there is no window where deliveries fail.

3

Let the old one expire

After all your instances run the new secret, you are done — the old one stops co-signing when the grace window ends.

Because your verifier already accepts any matching candidate in a comma-separated Drazill-Webhook-Signature header (see Signing & verification), you need no special-casing for the rotation window — the same verify code handles one or two candidates.

Drazill-Webhook-Secret-Version always reports the active secret’s version, even during a two-candidate grace window (the retired co-signer’s version is not surfaced in a header). Don’t assume the version maps one-to-one to which candidate verified — verify against every candidate.

Backend and both SDK verifiers are tested against multi-candidate headers (tests/test_webhooks.py::test_rotation_grace_window_signs_with_both_secrets, sdks/python/tests/test_webhooks.py, sdks/typescript/src/__tests__/webhooks.test.ts).