TL;DR: Accept both the old and the new webhook secret for a short overlap window after rotation, then retire the old one. The agent's stored secret went stale the moment the subscription secret rotated, so every legitimate event now fails verification. Update the secret store first, confirm signatures verify again, then drop the old secret.

```text
signature verification failed
```

1. Confirm a rotation actually happened: check who rotated the Greenhouse webhook secret and when, and compare with when verification failures started. Expected: the first failure timestamp lines up with the rotation.
2. Add the new secret alongside the old one: configure the handler to try verification with the new secret first, then fall back to the old secret, instead of swapping in a single secret. Expected: the handler config lists two accepted secrets with an expiry date on the old one.
3. Verify against the raw request body: make sure the handler computes the signature over the exact bytes Greenhouse sent, not over re-serialized JSON (re-serialization changes whitespace and breaks the signature). Expected: a test event signed with the new secret verifies cleanly.
4. Compare signatures in constant time: use your language's constant-time compare for the check so the verification cannot leak the secret through timing. Expected: mismatched signatures fail closed with no measurable timing difference.
5. Retire the old secret after the overlap window (24 to 48 hours is plenty): remove it from the accepted list and watch the failure log go quiet. Expected: zero verification failures for a full day with only the new secret active.

## Use this when
- Greenhouse webhook events suddenly all fail signature verification right after someone rotated the webhook secret
- The agent rejects events that Greenhouse shows as delivered successfully
- Verification worked yesterday and nothing else in the handler changed

## Not for this skill when
- The failure started with no rotation involved (suspect body-encoding changes or a wrong secret from the start instead)
- Greenhouse is returning 4xx/5xx on API calls rather than webhook delivery issues
- The events never arrive at all (that is a subscription or firewall problem, not a signature problem)

## Variant phrasings
- Greenhouse webhook HMAC verification failing after secret rotation
- webhook signature mismatch on Greenhouse events
- Greenhouse webhooks rejected as invalid after changing the signing secret

## Why it happens
Webhook verification compares a signature computed with the secret the agent has stored against the signature Greenhouse computed with the secret on the subscription. Rotation changes the subscription side instantly, but the agent's stored copy only changes when someone updates it. Until then every event looks forged to the agent, even though Greenhouse sent it legitimately.

## Edge cases
- If the handler reads the secret once at startup, updating the secret store is not enough - restart the handler or make it reload the secret per request.
- A proxy or middleware that parses and re-serializes the JSON body will break signatures even with the right secret; always sign the raw bytes.
- Do not extend the overlap window indefinitely; an old secret that never retires is a second live credential.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_5rn64CmI1DpkksLF4veh9A
