Having Trouble Validating Webhook

Hi,

I’m trying to verify my webhook vai c# and it is not working. Can I please get some assistance on my code to see where I’m going wrong?

Thank you.

       log.Info("Recieved validation request");
        JObject joPayload = JObject.Parse(payload);
        var keySent  = (string)joPayload.SelectToken("payload.plainToken");
        log.Info("Key Sent: " + keySent);

        string hashString = "";
        var secret = keySent;
        var message = keySent;
        var encoding = new System.Text.ASCIIEncoding();
        byte[] keyByte = encoding.GetBytes(secret);
        byte[] messageBytes = encoding.GetBytes(message);
        using (var hmacsha256 = new HMACSHA256(keyByte))
        {
            byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
            hashString = Convert.ToBase64String(hashmessage);
        }

        StringBuilder sb = new StringBuilder();
        foreach (char t in hashString)
        { 
            //Note: X for upper, x for lower case letters
            sb.Append(Convert.ToInt32(t).ToString("x2")); 
        }

        response = "{\"plainToken\": \"" + keySent +"\",\"encryptedToken\": \"" + hashString + "\"}";
        log.Info("Response: " + response);

Hi @ahavatammi
Thanks for reaching out!
Have you been able to troubleshoot this? or do you still need assistance here?
I will take a look into this in the meantime!
Best,
Elisa

Hi Elisa,

Yes, can I please get some assistance? I have read many articles and still am unable to get this to work.

Thank you,
Jeff

Hi @ahavatammi
Here is a code snippet that might be helpful

const crypto = require('crypto')

const message = `v0:${request.headers['x-zm-request-timestamp']}:${JSON.stringify(request.body)}`

const hashForVerify = crypto.createHmac('sha256', ZOOM_WEBHOOK_SECRET_TOKEN).update(message).digest('hex')

const signature = `v0=${hashForVerify}`

if (request.headers['x-zm-signature'] === signature) {
  // Webhook request came from Zoom
} else {
  // Webhook request did not come from Zoom
}

And also the link to our sample app

Note how you would have to construct a message string first and then you would pass that message down to validate the URL

Hope this helps

Hi Elisa,

Thank you for the code sample.

I was able to figure it out with the help of Free HMAC-SHA256 Online Generator Tool | Devglan. I used it to the create the return value and then compare it to my code.

Here is a working example for those coming behind me.

        private static string UrlValidation(string sig, string tStamp, string payload, ILogger log)
        {

            log.LogInformation("Recieved validation request");

			// get the plainToken
            JObject joPayload = JObject.Parse(payload);
            var plainToken = (string)joPayload.SelectToken("payload.plainToken");
            log.LogInformation("plainToken: " + plainToken);

            // secret token from the marketplace app
            string secretToken = "<your token here>";

            string message = plainToken;
            
            // convert secret token to bytes
            byte[] secretTokenByte = new UTF8Encoding().GetBytes(secretToken);
            // convert the plainToken sent in to bytes
            byte[] messageBytes = new UTF8Encoding().GetBytes(message);
            // hash it
            byte[] hashmessage = new HMACSHA256(secretTokenByte).ComputeHash(messageBytes);
            // to lowercase hex
            string hexMessage = String.Concat(Array.ConvertAll(hashmessage, x => x.ToString("x2")));
            // create return message
            string response = "{\"plainToken\": \"" + plainToken + "\",\"encryptedToken\": \"" + hexMessage + "\"}";

            log.LogInformation("Response: " + response);

            return response;
        }

Hi @ahavatammi
Thanks for sharing your findings with the community!
Feel free to reach out back to us if you need anything else!
Best,
Elisa

I did try with UrlValidation function which is provided in the sample code, it returns the value which is not same as the x-zm-signature value. Am I missing anything?

@ahavatammi , I’m not seeing plainToken in the payload object. How did you get that value?

Just to add, the response message should be serialize before passing it to the response. Below is my c# code sample that works

[HttpPost(“webhook”)]
public IActionResult Post([FromBody] JObject body)
{
var response = new JObject();

Console.WriteLine(Request.Headers);
Console.WriteLine(body);

// Construct the message string
string timestamp = Request.Headers["x-zm-request-timestamp"];
string message = $"v0:{timestamp}:{JsonConvert.SerializeObject(body)}";
string hashForVerify = string.Empty;
string hashForValidate = string.Empty;

// Create a hash-based message authentication code (HMAC) using the SHA-256 hash function
using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes("ThisShouldBeYourSecretToken")))
{
    byte[] hashValue = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
    hashForVerify = BitConverter.ToString(hashValue).Replace("-", "").ToLower();
}

// Hash the message string with your Webhook Secret Token and prepend the version semantic
string signature = $"v0={hashForVerify}";

// Validate the request
if (Request.Headers["x-zm-signature"] == signature)
{
    // Zoom validating you control the webhook endpoint
    if (body["event"].ToString() == "endpoint.url_validation")
    {
        using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes("ThisShouldBeYourSecretToken")))
        {
            byte[] hashValue = hmac.ComputeHash(Encoding.UTF8.GetBytes(body["payload"]["plainToken"].ToString()));
            hashForValidate = BitConverter.ToString(hashValue).Replace("-", "").ToLower();
        }

        response["message"] = new JObject
        {
            { "plainToken", body["payload"]["plainToken"] },
            { "encryptedToken", hashForValidate }
        };
        response["status"] = 200;

        return Ok(JsonConvert.SerializeObject(response["message"]));
    }
    else
    {
        response["message"] = "Authorized request to Zoom Webhook sample.";
        response["status"] = 200;

        Console.WriteLine(response["message"]);

        // Business logic here, example make API request to Zoom or 3rd party

        return Ok(response);
    }
}
else
{
    response["message"] = "Unauthorized request to Zoom Webhook sample.";
    response["status"] = 401;

    Console.WriteLine(response["message"]);

    return Unauthorized(response);
}

}