Skip to main content
Mizaniya Pay VTPE sends webhooks with an 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 the X-Timestamp header value (no delimiter or separator). The resulting digest is plain hex and placed in the X-Signature header with no prefix.
For example, if the raw body is {"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.

Code Examples

Always read the raw body before JSON parsing or body re-serialization. Re-encoding a parsed object may change field order or whitespace and cause the computed signature to differ from X-Signature, even for otherwise valid requests.
After verifying the signature, also validate that X-Timestamp is within plus or minus five minutes of your server’s current time. Rejecting old timestamps prevents replay attacks where an attacker resends a captured webhook request.