The header
Verifying it
Compute the same digest over the body you received and compare it with the header.Two things that break verification
Sign the raw body, not a re-serialised object. If your framework parses the JSON and you re-encode it to verify, a different key order or spacing produces a different digest and every delivery fails. Capture the raw bytes before parsing. Compare in constant time.crypto.timingSafeEqual and hmac.compare_digest above exist for this. A plain === on a secret-derived value leaks information about it.