X-Signature header so you can verify that each event truly came from VTPE. This page explains how the signature is computed and how to validate it in your server code.
How the Signature Is Computed
VTPE signs every webhook using HMAC-SHA256 with your HMAC Secret. The message input is the raw request body concatenated directly with theX-Timestamp header value (no delimiter or separator). The resulting digest is plain hex and placed in the X-Signature header with no prefix.
{"event":"payment.success","data":{"reference":"ORD-123"}} and X-Timestamp is 1718000000, the signed message is exactly:
Verification Steps
Use raw request bytes (before any JSON parsing) when computing the signature. Polling or re-serializing the payload will change whitespace and break verification.1
Read the raw body before JSON parsing
Capture the exact bytes VTPE sent. In Express, use
express.raw(). In Flask, use request.get_data().2
Read the X-Timestamp header
Capture the exact
X-Timestamp string value from the incoming request headers.3
Read the X-Signature header
Capture the exact
X-Signature hex string sent by VTPE.4
Compute the expected signature
Calculate
HMAC-SHA256(rawBody + timestamp, HMAC_SECRET) and encode the result as a lowercase hex string.5
Compare signatures in constant time
Use a timing-safe comparison function (for example,
crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python).6
Return HTTP 200 on success, 400 on failure
If the signatures match, parse the body and respond with
200 and {"success": true}. If they do not match, respond with 400.