✍️ Header signature
We sign the webhook events by including a signature in each event’s Tomorro_Signature
header. Each webhook has a secret autogenerated and shared between us and the end user.
Then, to sign the event, we compute an HMAC-SHA256 of the timestamp and the event’s body:
sha256 = HMAC_SHA256(timestamp + “.” + eventBody)
Finally, the Tomorro_Signature
header has the following format:
Tomorro-Signature: t=1492774577, sha256=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
with t
the timestamp and sha256
the previously computed hash.
Note that newlines have been added for clarity, but a real Tomorro_Signature
header is on a single line.
🔍 Signature check
When a client receive a webhook event, he should check that the signature is valid. Here is an example of how to do it:
import {createHmac, timingSageEqual} from 'crypto';
function checkSignature(signature, secret, body) {
const timestamp = signature[0].split('=')[1];
const sig = Buffer.from(signature[1].split('=')[1], 'utf8');
const hmac = createHmac('sha256', secret);
const digest = Buffer.from(hmac.update(timestamp + '.' + JSON.stringify(body)).digest('hex'), 'utf8');
if (sig.length !== digest.length || !timingSafeEqual(digest, sig))
{ return false; }
else { return true; }
}
Note that it’s recommended to use a time constant-time string comparison to avoid timing attack vulnerability, that’s why we are using here timingSafeEqual
instead of ===
(see https://en.wikipedia.org/wiki/Timing_attack)
Moreover, the client should check if the difference between the received timestamp et the current timestamp is within his tolerance (this is to avoid replay attack).