New App Validation Failure

I am attempting to set up a new " Server-to-Server OAuth" app before full depreciation of JWT apps takes effect.

I’m working on the endpoint validation per the specifications here:

I have attempted to implement this and have used the discussion here for reference:

However, this is not working. I receive the unspecific error message: “URL validation failed. Try again later.”

How can I get more information about why this is failing?

I am not sure if:
(a) I’m using the wrong token.
(b) There is a flaw in my hash code.
(c) There is another requirement of the response that is not clearly documented.
(d) There is
(e) Something else

@benkatz , could you share your validation code here?

Thanks @chunsiong.zoom

public async Task<IHttpActionResult> ZoomApi()
	string content = await Request.Content.ReadAsStringAsync();
	JObject jsonObject = JObject.Parse(content);
	JObject payload = (JObject)jsonObject["payload"];
	string plainToken = payload["plainToken"].ToString();
	string encryptedToken = GetHash("plainToken", StringHelpers.ZoomStsSecretToken);
	return Json(new { plainToken, encryptedToken });

private string GetHash(String text, String key)
	var encoding = new System.Text.ASCIIEncoding();
	var sha256 = new System.Security.Cryptography.HMACSHA256();
	sha256.Key = encoding.GetBytes(key);
	var hash = sha256.ComputeHash(encoding.GetBytes(text));
	var hashed = ToHex(hash, false);
	return hashed;

private string ToHex(byte[] bytes, bool upperCase)
	StringBuilder result = new StringBuilder(bytes.Length * 2);
	for (int i = 0; i < bytes.Length; i++)
		result.Append(bytes[i].ToString(upperCase ? "X2" : "x2"));
	return result.ToString();

@benkatz the code looks fine. Are you using the secret token instead of validation token?

@chunsiong.zoom I have tried both. In both cases, I’m getting the exact same response.

Is there any way to get a more detailed response as to the problem or another way to get validated?

@benkatz , I don’t have sample code in C#, but here’s how I’m doing it in nodejs JavaScript.

It will check the POST request body, for key value pair where event = ‘endpoint.url_validation’,
There after it does something similar to your code.

if (req.method === 'POST') {
  // Check if the event type is "endpoint.url_validation"
  if (req.body.event === 'endpoint.url_validation') {
    const hashForValidate = crypto.createHmac('sha256', secretToken)

      "plainToken": req.body.payload.plainToken,
      "encryptedToken": hashForValidate
  } else {
    // Write the request data to a file
    fs.writeFile(filePath, JSON.stringify(req.body), 'utf8', function (err) {
      if (err) {
        return console.log(err);
      console.log(`The file ${filePath} was saved!`);


@chunsiong.zoom But you’re telling me that my code looks fine. What am I supposed to do at this point?

Again, is there any way to get a more detailed error message or another way to validate?

@benkatz ,

Here’s what I’ll do if i was debugging.

Run the code on debug mode, use postman to do a HTTP Post to my localhost debug port with this specific payload

  "payload": {
    "plainToken": "qgg8vlvZRS6UYooatFL8Aw"
  "event_ts": 1654503849680,
  "event": "endpoint.url_validation"

Make sure I distinguish between validation event type and actual webhook events.

@benkatz ,

I’ve just tried draft up a quick sample code (not perfect) on ASP.NET core based on my recommendations above, and have validated it successfully
I hope this helps

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var app = builder.Build();
// Configure the HTTP request pipeline.


app.MapPost("/webhook", async context =>
    // Get the request content as a string asynchronously
    string requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();

            JObject jsonObject = JObject.Parse(requestBody);
            JObject payload = (JObject)jsonObject["payload"];
            string plainToken = payload["plainToken"].ToString();
            string encryptedToken = GetHash(plainToken, "yoursecrettokenhere");

            var response = new
             context.Response.ContentType = "application/json";

        // Write the JSON response
        await context.Response.WriteAsync(JsonConvert.SerializeObject(response));

    catch (Exception ex)
        await context.Response.WriteAsync("Error: " + ex.ToString());


 string GetHash(string text, string key)
    using (HMACSHA256 sha256 = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
        byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
        return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();


@chunsiong.zoom Thank you for your continued help, but I am still not able to get this to work. My test post is returning a response in what appears to be the proper format:


Two question:

  1. Should I be using the “Secret Token” or the “Verification Token”. I’ve been trying both but having a clear answer on that would eliminate a bit of uncertainty.

  2. Is there any way to get more information about why this is failing? In particular it would be useful to know if Zoom is not getting values for the returned tokens or if there is a problem with the returned values.

@benkatz ,

you should be using secret token, in addition do ensure that the content type returned i “application/json”;