Original publication date: May 13, 2020
Question:
We can't get signature verification for webhooks to work. How can we check if we really got a correct secret? We generated the HMAC with SHA256 hash function using NodeJS’s inbuilt crypto library, but it does not give a matching result.
Answer:
Please check that you are following the required steps regarding concatenation of the timestamp:
- Extract the timestamp and signature from the cos-signature header.
From the example in the documentation, that header should be similar to this:
cos-signature:t:2019-04-02T11:33:26.6672036-04:00,
v1:adT6JQ7y+fbL2a0uq4infc6VOX+VPyJizRTMSz158Vs= - Concatenate the timestamp with the event body with a period in between the two values. The section with added bold emphasis is the timestamp string (see example above) that should be concatenated with a period (".") to the webhook body.
- Compute an HMAC with the SHA256 hash function. Use the signing secret (provided by your COS integration team) as the key, and use the string created in step #2, as the message. Make sure to convert the HMAC SHA256 to Base64, as our signature hashes are base64.
- Compare the hash generated in step #3 with the signature extracted from the cos-signature header. If the hash and signature match, then this is a valid request from COS.
- Compare the timestamp to the time received. If the difference exceeds your tolerance for accepting messages, you can reject the message or flag for further review.
For general information regarding webhook signature verification, including an example of the concatenated timestamp and webhook JSON, see: https://docs.crbcos.com/docs/webhook-signatures
The following code enables webhook signature validation:
const crypto = require('crypto');
function validateSignature(header, payload, signingSecret) {
let headerParts = header.split(',');
let timestamp = headerParts[0].replace('t:', '');
let signature = headerParts[1].trim().replace('v1:', '');
let secret_buffer = Buffer.from(signingSecret, 'base64');
let hmac = new crypto.createHmac('sha256', secret_buffer);
let hmac_digest = hmac.update(`${timestamp}.${payload}`, 'binary').digest('base64');
console.log(hmac_digest);
return hmac_digest === signature;
}
let payload = '{"id":"e7ead744-d6ff-4521-863d-abab0176f849","eventName":"Core.Transaction.Completed","status":0,"partnerId":"d6b4c661-b38a-46a3-8963-a9a40131eacf","createdAt":"2020-04-28T18:45:14.57-04:00","resources":["core/v1/transactions/6aa7e3b2-3c85-4647-aa6f-abab0176e18b"],"details":[{"transactionId":"6aa7e3b2-3c85-4647-aa6f-abab0176e18b","transactionCode":"Account Transfer","debitSubAccount":"2058112745","debitMasterAccount":"2058112745","debitResult":"OK","creditSubAccount":"2101120877","creditMasterAccount":"2101120877","creditResult":"OK","rail":"Internal","railId":"0","amount":"100"}]}';
let signatureHeader = 't:2020-04-28T18:45:15.6360965-04:00, v1:MvGXdx1O1P8+YjWglbmxAxkrAgVlMglSPpCzsR/Ly/w=';
let secret = 'uVdwwB9HIFZ+5/8nmta5PXu6p1kxZcQmXPCNBRhiVNuKNBhIgth8MvmlD7FYoVfHOmcpHO5QYN/3HHnJ+6TO6Q==';
let result = validateSignature(signatureHeader, payload, secret);
console.log(result);