Webhook URL Validation

Format Your New Topic as Follows:

API Endpoint(s) and/or Zoom API Event(s)
Implementing CRC validation for “endpoint.url_validation”. Currently configured with multiple JWT Sub Accounts per environment to listen for Meeting and Recording Callback Events.

For the CRC validation response, it states that the plain token in the request needs to be hashed using (HMAC) SHA-256 hash, which requires using secret token string. Our question is we have multiple JWT (sub) accounts setup by environment/campus, anywhere from two to five secret key configurations per environment. While processing this incoming request, how would we determine which secret token string to use for the hash?


How To Reproduce

Hi @kupadhyaya
Thanks for reaching out to the Zoom Developer Forum!
Before we start talking about the way to determine which secret token string you need to use to hash, I just wanted to mention that the JWT app will be completely deprecated by September 1st, so I would highly encourage you to start migrating from your JWT app to a Server to Server Oauth app

Hi Elisa,

Thank you for getting back to me. Yes, we just realized that we need to migrate our JWT apps before the September 1st deadline. We have started going through the migration documentation and will soon be starting that work to convert our apps to the server to server oauth apps in parallel.
However, are you saying that the webhook validation is not necessary? Isn’t there a window where webhook validation will be required and before we have to migrate from JWT apps? Also, I did find a post with a suggested solution to inspect the authorization header attribute value, which is currently the verification token and compare that against all subaccounts to determine which hashed value response needs to be returned to Zoom. Is this the correct approach?


Hi @kupadhyaya
Sorry if I shared information that might have been confusing.
The webhook validation will be required and it is necessary for any app type that you develop.
Here’s a link to our Docs that covers webhook validation


Hi Elisa,

No worries and thanks for the clarification. We are in the process of implementing webhooks validation and starting on the JWT migration soon as well. Is the approach mentioned above correct to validate the webhooks when you have multiple accounts/subaccounts?
Also, does the migration from JWT to Oauth app need to be completed by start or end of September?


Hi Elisa,

I have the code in place to generate the encrypted token and using ngrok to trigger and process the url validation event. I’m returning a json response with status 200, but still keep getting “URL validation failed. Try again later”. I verified everything but I don’t see any issues on our end, would your logs be able to tell why the validation is failing?
All our callback are being processed through a WCF Service. I’m testing our CTU REG subaccount, since we’re not able to save it after a previous subscription change.


Subaccount: CTU_REG
Temp URL: https://6726-216-49-218-2.ngrok-free.app/CEC.Svcs.Zoom.Adapter/ZoomService.svc/REST/ProcessCallbacks
Verification Token: ending in “…vZXw”

Sample Response:

Any help would be greatly appreciated.


Hi Elisa,

I noticed that there was a minor typo in the “encryptedToken” property, which was fixed and I’m also constructing the response exactly as shown in the examples. I tried the hash both with the Verification and Secret tokens, but neither responses result in a successful validation. Here is the sample response as seen in ngrok interface. Not sure what I’m missing???

Hi Elisa,

Just wanted to provide an update here, we were finally able to successfully validate our endpoint URLs. Basically there was an issue with the way the hash was being generated and I also had to slightly tweak how JSON response was being returned by our WCF Service. Just a suggestion, a more informative error message or logs from clicking the ‘Validate’ button would be really helpful from Zoom’s end.

For anyone else struggling with the validation, hopefully one or more of the steps below might be helpful!

  1. If you have multiple sub accounts, look at the code example provided here on how to properly validate using the header value since only a plain token is provided in the Zoom request,


  1. If validation is not working, clone the version of Tommy Gaessler’s webhook sample application in NodeJS locally and set it up to see if your Account Secret token(s) validates correctly, this was crucial for us and helped confirm that there was an issue in our code for the failure.


  1. Here is another related posted, where a user has provided an example of code in C# for URL Validation, which is also helpful to generate the Hash value correctly and how to format the response,


  1. This is a great tool to see if your HMAC algorithm is generating the hash value correctly,


  1. For testing, get a free account with ngrok to create a https secure tunnel/URL forwarding to develop and test your code. Here is their reference documentation on how to validate Zoom webhooks,




Hi @kupadhyaya
I am happy to hear that you were able to troubleshoot this on your end and I really appreciate you sharing this with the community!

1 Like

i followed all of the steps but still giving me wrong url
i am using c# code
string requestBody = await new StreamReader(_baseService.HttpContextAccessor.HttpContext.Request.Body).ReadToEndAsync();
_baseService.HttpContextAccessor.HttpContext.Request.Body.Position =0;
var timeSpane = _baseService.HttpContextAccessor.HttpContext.Request.Headers[“x-zm-request-timestamp”].ToString();
var EndZoomSessionWebhookDto = JsonConvert.DeserializeObject(requestBody);
var encoding = new System.Text.ASCIIEncoding();
var sha256 = new System.Security.Cryptography.HMACSHA256();
string message = $“v0:{timeSpane}:{requestBody}”;
sha256.Key = encoding.GetBytes(_zoomSetting.ZoomWebhookSecretKey);
var hash = sha256.ComputeHash(encoding.GetBytes(message));

//using (var hmacsha256 = new HMACSHA256(encoding.GetBytes(_zoomSetting.ZoomWebhookSecretKey)))
//var hashForVerify = hmacsha256.ComputeHash(encoding.GetBytes(message));

var hashed = ToHex(hash, false);

string signature = $“v0={hashed}”;

if (signature == _baseService.HttpContextAccessor.HttpContext.Request.Headers[“x-zm-signature”]) {
if (EndZoomSessionWebhookDto.@event == “endpoint.url_validation”) {
var plain = new System.Security.Cryptography.HMACSHA256();
plain.Key = encoding.GetBytes(_zoomSetting.ZoomWebhookSecretKey);
var hashForValidate = plain.ComputeHash(encoding.GetBytes(EndZoomSessionWebhookDto.payload.plainToken));

     //var hashForValidate = new HMACSHA256(encoding.GetBytes(_zoomSetting.ZoomWebhookSecretKey)).ComputeHash(encoding.GetBytes(EndZoomSessionWebhookDto.payload.plainToken));

     var hashString = ToHex(hashForValidate, false);

     //var obj = new DTO.ZoomDtos.Message
     //    plainToken = EndZoomSessionWebhookDto.payload.plainToken,
     //    encryptedToken = hashString

string response = “{"plainToken": "” + result.message.plainToken + “","encryptedToken": "” + result.message.encryptedToken + “"}”;

return Ok(response);