
Learn how to validate the X-Signature header on incoming webhook requests from Singa Payment Gateway using HMAC-SHA512. This guide covers the full signature algorithm, string-to-sign construction, and step-by-step validation.
All webhook requests from Singa Payment Gateway include an X-Signature header generated using HMAC-SHA512. To verify that a request is authentic and has not been tampered with, reconstruct the signature on your side and compare it with the received value.
StringToSign = METHOD + ":" + ENDPOINT + ":" + ACCESS_TOKEN + ":" + SHA256(normalizedBody) + ":" + TIMESTAMP
Signature = HMAC_SHA512(StringToSign, clientSecret)
| Component | Source | Description |
|---|---|---|
| METHOD | Fixed | Always POST for webhooks |
| ENDPOINT | Your webhook URL path | Path + query string, e.g. /webhook/va-transaction |
| ACCESS_TOKEN | Authorization header | Value after Bearer prefix |
| SHA256(normalizedBody) | Request body | SHA-256 hash of the JSON body after recursive key sorting |
| TIMESTAMP | X-Timestamp header | Unix timestamp in seconds |
| clientSecret | Merchant dashboard | Your Client Secret (HMAC key) |
Extract headers from the incoming request:
X-Signature — the signature to validateX-Timestamp — Unix timestamp in secondsAuthorization — extract the token after Bearer Normalize the JSON body:
Hash the normalized body with SHA-256 (hex digest)
Build the string to sign:
StringToSign = POST:/your/endpoint:accessToken:hashedBody:timestamp
Compute HMAC-SHA512 of the string to sign using your clientSecret
Compare the computed signature with X-Signature using a constant-time comparison
<?php
function validateWebhookSignature($requestBody, $headers, $clientSecret, $endpoint) {
$receivedSignature = $headers['X-Signature'] ?? '';
$timestamp = $headers['X-Timestamp'] ?? '';
$authorization = $headers['Authorization'] ?? '';
$accessToken = str_replace('Bearer ', '', $authorization);
$bodyArray = json_decode($requestBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return false;
}
// Sort keys recursively
function sortRecursive(&$array) {
ksort($array, SORT_STRING);
foreach ($array as &$value) {
if (is_array($value)) {
sortRecursive($value);
}
}
}
sortRecursive($bodyArray);
$normalizedJson = json_encode($bodyArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$hashedBody = hash('sha256', $normalizedJson);
$stringToSign = "POST:{$endpoint}:{$accessToken}:{$hashedBody}:{$timestamp}";
$calculatedSignature = hash_hmac('sha512', $stringToSign, $clientSecret);
return hash_equals($calculatedSignature, $receivedSignature);
}
?>
hashedBody = SHA256( json_encode( sortKeysRecursive( json_decode(body) ) ) )
stringToSign = "POST" + ":" + endpoint + ":" + accessToken + ":" + hashedBody + ":" + timestamp
expectedSignature = HMAC_SHA512(stringToSign, clientSecret)
For complete implementation examples in PHP, Python, and Node.js, see the “How to Validate Signature” section on any individual webhook page (e.g. VA Transaction, Disbursement).