Implement the webhook event signature validation

Low difficulty

5 min read time

15 min implementation time


How to validate the signature

When you subscribe to a webhook event to receive signal updates from a vehicle, you will receive POST requests with the X-Hub-Signature header.

This header represents the message signature that allows checking the authenticity of the request.

How can we validate it?

We need to check if the signature is the concatenation of two strings separated by the = character:

  • The first string has to be the name of a hash function for example, MD5, SHA1, SHA256 or others. Let us call it algorithm.

๐Ÿ“˜

Info

This system currently uses the SHA256 hash function.

  • The second string is the HMAC using the hash function named in the first string.
    Let us call it HMAC(message, secret, algorithm).

The signature will have a form like the following:
algorithm=HMAC(message, secret, algorithm).

๐Ÿ“˜

Info

If you don't remember what we are talking about, please visit the Receiving signal guide's page.


What is HMAC

In cryptography, an HMAC is a specific type of message authentication code, MAC, involving a cryptographic hash function and a secret cryptographic key.

To validate the HMAC, we need to generate it using the following parameters:

  • A message that is the body of the POST request we are receiving.
  • A secret, that is the โ€˜hub.secretโ€™ property we used in the body of the request made at the moment of the webhook subscription.
  • A hash function, which is written in the first string of the signature as explained before.

๐Ÿšง

Warning!

Both the secret and message have to be encoded in utf-8.
Be careful when parsing the message, the body of the request in our case, because it has to be a text!



Code

import * as crypto from "crypto";
 
const supportedAlgorithms = ["sha256"];
 
const isSignatureValid = (message: string, secret: string, signature: string): boolean => {
   const parts = signature.split("=");
   if (parts.length !== 2) {
       return false;
   }
   const algorithm = parts[0];
   if (!supportedAlgorithms.includes(algorithm)) {
       return false;
   }
   const hmac = crypto.createHmac(algorithm, secret)
       .update(message, "utf8")
       .digest();
   const signatureBuffer = Buffer.from(parts[1], "hex");
   return crypto.timingSafeEqual(signatureBuffer, hmac);
};

How this example works

If you want to have feedback if you are implementing it in the right way, here we show a practical example of a signature generated from the following parameters:

  • Message:
{"topic":"vehicle:7d42d670-6a96-4ff0-ab63-5d6673967d2d:generic:autonomy_meters","payload":{"data":{"meters":24000},"timestamp":1614594977551,"deliveryTimestamp":1614594977563}}
  • Secret: this_is_a_$ecret.
  • Hash function: sha256.

The generated signature will be: sha256=bb2c166d254838b72bd78b0486d804cef58bd36c987d12147d554b45700e69f4.

๐Ÿ‘

Congrats!

Now, you have a simple method to validate a signature.