Securing Zoho Sign Webhooks with HMAC authentication

Webhooks Security

Zoho Sign offers you an option to secure your webhooks by using HMAC-SHA 256 (Hash-Based Message Authentication Codes with SHA 256), an industry standard hashing mechanism to ensure the authenticity and integrity of the webhook is intact. Securing a webhook with HMAC will help check:
  1. If the webhook request has been sent from Zoho Sign (The secret key must be known only to Zoho Sign and the receiving application).
  2. If the webhook content has been tampered with along the way (integrity).

How does webhook security work in Zoho Sign?

When a webhook is sent from Zoho Sign, a HMAC signature will be included in the request headers with the name X-ZS-WEBHOOK-SIGNATURE. Upon receiving the webhook request, the receiving application will generate a HMAC signature using the same secret key and compare the results with the value present in the request header. If the value matches, the data is legitimate; otherwise, the data has been tampered with.

Generating a HMAC signature

Zoho Sign calculates the signature of the webhook payload using the HMAC-SHA256 algorithm, and the result is sent in base64 format in the request header. Here is the explanation with sample data:

payload
{{"requests":{"request_name":"Test Name"},"notifications":{"operation_type":"RequestSigningSuccess"}}
secret_key
thisisthesamplekeyfortestingpurposes
base64encode(HMAC SHA-256(payload+secret_key))
drbSrM4H816RYKpZiRBLddUa0yHaTrwjtY04sIZFZus=

Here is an image of how this webhook request header (HMAC header) will look like:


Verifying HMAC signature in the receiving application

  1. You must read the payload as a string to avoid reordering keys when read in JSON format.
  2. Compute HMAC SHA-256 hash of the payload using the secret key and base64 encode the result.
  3. Compare the value obtained from step 2 and the received HMAC header (X-ZS-WEBHOOK-SIGNATURE) value. If there is a mismatch, reject the webhook request.


Sample java cope snippet to verify the HMAC signature

  1.     private static String verifyHmacHash(String secretKey, String payload, String hmacHash) throws Exception 
  2.     {
  3.         String macAlgoName = "HmacSHA256";
  4.         byte[] secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
  5.         Mac mac = Mac.getInstance(macAlgoName);
  6.         SecretKeySpec keySpec = new SecretKeySpec(secretKeyBytes, mac.getAlgorithm());
  7.         mac.init(keySpec);
  8.         byte[] macData = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
  9.         String calculatedHmac = java.util.Base64.getEncoder().encodeToString(macData);
  10.         
  11.         if(hmacHash.equals(calculatedHmac))
  12.         {
  13.         System.out.println("Hashes match, Webhook payload is valid!!");
  14.         }
  15.         else
  16.         {
  17.         System.out.println("Hashes doesn't match, Webhook payload is tampered!!");
  18.         }
  19.         return calculatedHmac;
  20.     }