12 October 2023

HTTP(s) Notification Signature

Added custom request header for security check of HTTP(s) notifications.

For each HTTP(s) notification we add special HTTP header VG-Signature which contains at least 2 comma separated parameters (in future we may add more):

  • t – timestamp
  • v1 – hash-based message authentication code (HMAC) with SHA-256
VG-Signature: t=[TimeStamp],v1=[Signature]

To verify signature, you should complete the following steps:

  • Split the header using the , character as the separator to get a list of elements. Split each element using the = character as the separator to get a prefix and value pair. The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature.
  • Prepare the signed_message string. The signed_message string is created by concatenating:
    • the timestamp (t value)
    • the . character
    • request body
  • Compute an HMAC with the SHA256 hash function. Use your api key as the key, and use the signed_message string as the message.
  • Compare the signature in the header to the expected signature.

You may also compute the difference between the current timestamp and the received timestamp, and decide if the difference is within your tolerance.

Implementation samples

const {createHmac} = require('crypto');
function verify_signature (header, request) {
    const [[,timestamp],[,signature]] = header.split(',').map(p => p.split('='))
    const signed_message = `${timestamp}.${request}`;
    const hmac = createHmac('sha256', '<<key>>');
    const expected_signature = hmac.update(signed_message).digest('hex');
    return expected_signature === signature;
}
const header = 't=[timestamp],v1=[signature]';
const request = '[HTTPRequestBody]';

console.log(verify_signature(header, request) ? 'ok' : 'fail');
function verify_signature (string $header, string $request)
{
    [[$t, $timestamp], [$sgn, $signature]] = array_map(
        fn ($p) => explode('=', $p),
        explode(',', $header)
    );
    $signed_message = sprintf('%s.%s', $timestamp, $request);
    $expected_signature = hash_hmac(
        'sha256',
        $signed_message,
        '<<key>>'
    );
    return $expected_signature === $signature;
}
$header = 't=[timestamp],v1=[signature]';
$request = '[HTTPRequestBody]';

echo verify_signature($header, $request) ? 'ok' : 'fail';
import hmac
import hashlib
def verify_signature(header, request):
    ts, sgn, * other = header.split(',')
    t, timestamp, * other = ts.split('=');
    v, signature, * other = sgn.split('=');
    signed_message = '{}.{}'.format(timestamp,request)
    expected_signature = hmac.new(
        bytes("<<key>>", 'utf-8'), 
        bytes(signed_message, 'latin-1'), 
        hashlib.sha256
    ).hexdigest()
    return expected_signature == signature

header = "t=[timestamp],v1=[signature]"
request = "[HTTPRequestBody]"

print("ok" if verify_signature(header, request) else "fail")