Sample Code
PHP
/**
* check that the given API signature and timestamp are valid
* @param string $signature calculated from public $key and timestamp
* @param int $timestamp prevents reuse of an old signature
* @param string $key public key given to each organisation
* @return bool
*/
public static function validate(string $signature, int $timestamp, string $key): bool
{
// check if the timestamp is within 5 mins of now to prevent reuse of stolen signature
if (abs(time() - $timestamp) > 300) {
return false;
}
// get your secret from a database or environment
$apiSecret = ...
// perform your own encoding
$hmac = encode($key, $apiSecret $timestamp);
// does it match the given signature?
return hash_equals($hmac, $signature);
}
/**
* create a signature by applying the secret to key+timestamp
*/
public function encode(string $key, string $secret, int $timestamp): string
{
// hash using values provided
return base64_encode(
hash_hmac(
'sha256',
$key . $timestamp,
$secret
)
);
}
Java
public class WebhookSignatureValidator {
public boolean validateSignature(String body,
String signature,
String secretKey,
String timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
// Create the message to be signed by concatenating the message body and the timestamp
var message = body + timestamp;
// Initialize the MAC routine with the secret key
var mac = Mac.getInstance("HmacSHA256");
var secret = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secret);
// Create a digest of the bytes in the message
byte[] digest = mac.doFinal(message.getBytes());
// Print the digest as lower-case hex characters
var enc = DatatypeConverter.printHexBinary(digest).toLowerCase();
return signature.equals(enc);
}
}