X-Ryvo-Signature header with this structure:
t: Unix timestamp in seconds of when we signed the event.v1: hexadecimal HMAC-SHA-256 of${t}.${body_raw}using your signing secret.
How to verify
1
Read the header
Parse
X-Ryvo-Signature and pull out t and v1.2
Rebuild the expected signature
Compute
HMAC-SHA-256(secret).update(t + '.' + raw_body).digest('hex'). Use the raw body (the original string), not JSON that was parsed and re-serialized.3
Compare in constant time
Use a constant-time comparison (not
===) to avoid timing attacks.4
Check the timestamp
Reject events whose
t is more than 5 minutes old to mitigate replay attacks.Snippets
Common mistakes
I rebuild the JSON before signing
I rebuild the JSON before signing
The HMAC has to run against the raw body (the original string). If you parse and re-serialize, the JSON can come out with different spacing and the signature stops matching.
Comparing with === instead of constant time
Comparing with === instead of constant time
=== or strcmp bails out at the first differing byte, which leaks information through timing. Use crypto.timingSafeEqual (Node), hmac.compare_digest (Python), hmac.Equal (Go).I do not check the timestamp
I do not check the timestamp
Without a timestamp tolerance, an attacker who captures a legitimate event can replay it days later. Reject events whose
t is more than 5 minutes old.My framework consumes the body before I do
My framework consumes the body before I do
Some frameworks (Next.js API routes, NestJS) parse the body before handing it to the handler. Configure your route so you can reach the raw body before the JSON parse.