X-LazyApp-Signature is sha256= followed by an HMAC-SHA256 of the raw request body, keyed with your endpoint’s signing secret.
Implementation
Three ways this goes wrong
Comparing the parsed body instead of the raw bytes
Comparing the parsed body instead of the raw bytes
The most common mistake. Any JSON framework that re-serialises the body will reorder
keys or change whitespace, and the HMAC will never match. Capture the raw bytes before
parsing.
Using == instead of a constant-time compare
Using == instead of a constant-time compare
A normal string comparison returns early on the first differing byte, which leaks the
secret to an attacker who can measure it. Use
timingSafeEqual, hmac.compare_digest
or hash_equals.Verifying, then trusting the body's own ids
Verifying, then trusting the body's own ids
A valid signature proves the payload came from LazyApp, not that it is fresh.
Deduplicate on
X-LazyApp-Delivery so a replayed delivery cannot double-apply.Rotating a secret
UsePOST /v1/webhooks/{webhook}/rotate-secret or rotate from the console. Replays continue to verify because they are re-signed with the current secret by default.
Meta’s inbound signature
Separately, LazyApp verifies Meta’s ownX-Hub-Signature-256 on every inbound WhatsApp webhook and deduplicates redelivered events before they reach your workspace. You do not need to handle that — it happens before anything is written.