X-Novatrade-Signature header. Always verify the signature before
processing — never trust the payload otherwise.
Signature format
t— Unix timestamp (seconds) of when the signature was generated.v1— HMAC-SHA256 hex digest of<timestamp>.<raw_request_body>, keyed by your endpoint’s secret.
vN prefixes. Accept any known
version; reject unknown.
Verification algorithm
- Parse the header into
tandv1parts. - Build the signed string:
<t>.<raw_body>(period-separated). - Compute
HMAC-SHA256(secret, signed_string)and hex-encode. - Constant-time compare against the
v1value. - Reject if the timestamp is more than 5 minutes in the past (replay protection).
Implementation examples
Common mistakes
Verifying against a parsed/pretty-printed body
Verifying against a parsed/pretty-printed body
The signature is computed over the raw request bytes. Any parser that
normalizes whitespace or reorders keys breaks verification. Capture the
raw body before JSON parsing.In Express, use
express.raw({ type: 'application/json' }) for the
webhook route and parse JSON yourself after verification.In Spring Boot, bind the handler parameter as byte[] or String, not
a typed DTO, and parse after verification succeeds.Accepting events with expired timestamps
Accepting events with expired timestamps
Enforce the 5-minute replay window. Without it, a leaked signed request
can be replayed indefinitely. Reject requests with timestamps older than
your window.
Using the wrong secret
Using the wrong secret
Each registered endpoint has its own secret. If you have multiple webhook
endpoints (e.g. per partner), route by
X-Novatrade-Event-Id or
endpointId (where available) and use the corresponding secret.String equality instead of constant-time compare
String equality instead of constant-time compare
a === b in JavaScript exits early on first mismatched character, leaking
timing info. Use crypto.timingSafeEqual. Same in every language.Next
Event catalog
Payload schemas for every event type.
Replay and test
Inspect delivery history and manually replay events.