New Webhook URL Validation issue

I had a support ticket that got escalated with final email indication to post in here.

We have groups that integrate into our platform using JWT app and we all of the sudden are getting reports on this when they are trying to follow through with zoom integrations with the event notification endpoint URL. Basically a “URL Validation failed. Try again later.”

We have verified that the webhook endpoint requirements are being met. The webhook url hasn’t changed in years.

  1. It is https://
  2. We verified via a 3rd party tool that TLS 1.2+ is required and SSL cert is issued by a CA. SSL Server Test: manager.ce21.com (Powered by Qualys SSL Labs)
  3. The domain is FQDN. manager.ce21.com
  4. The endpoint does accept POST with json body
  5. and will return the appropriate HTTP status code.

Is there any indication of which of these is causing the validation failure?

Hey @reid,

Sometimes validation can fail because you aren’t setting the status correctly, or the encryption token that was sent back doesn’t match the encryption token in the header.

Can you confirm that the status is being set to 200 or 204 and that the encryption tokens match?

@reid Hope you will be fine.

Which platform you are using e.g NodeJS or no-code platforms?

Here is NodeJS validation sample :point_down:

app.post('/api/zoom/events', async (req, res) => {
        if (req.body.event == 'endpoint.url_validation') {
            let encryptedToken = crypto.createHmac('sha256','Your Secret Token').update(req.body.payload.plainToken).digest('hex');

            return res.json({
                plainToken: req.body.payload.plainToken,
                encryptedToken: encryptedToken
            })
        }
    });

Here is the session → How to enable Zoom WebHooks.

I’m trying to validate the webhook URL in Zoom using AWS Lambda and getting error as “URL Validation failed. Try again later" and not able to validate the URL.I have followed the same steps which is given in Documentation .

I can able to validate the URL by using VS code and facing this issue only on AWS Lambda.

I’m facing the issue while checking the condition if (req.headers[‘x-zm-signature’] === signature) ,x-zm-signature and signature is getting varied.

Here is NodeJS validation sample :point_down:

const crypto = require(“crypto”);

function oauthValidator(req){
var response

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

const hashForVerify = crypto.createHmac(‘sha256’, process.env.ZOOM_WEBHOOK_SECRET_TOKEN).update(message).digest(‘hex’)

const signature = v0=${hashForVerify}

if (req.headers[‘x-zm-signature’] === signature) {

if(req.body.event === 'endpoint.url_validation') {
  const hashForValidate = crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET_TOKEN).update(req.body.payload.plainToken).digest('hex')

  response = {
    message: {
      plainToken: req.body.payload.plainToken,
      encryptedToken: hashForValidate
    },
    status: 200
  }
  
  return {
    "status":response.status,
    "message":response.message
  }

} else {
  response = { message: 'Authorized request to Webhook Sample Node.js.', status: 200 };
  
  return {
    "status":response.status,
    "message":response
  };

}

} else {

response = { message: 'Unauthorized request to Webhook Sample Node.js.', status: 401 };

return response;

}
}

Please help me on to get this validation done. Thanks…!

Hi @Uma , did you find the solution with aws lambda? I am also facing same issue, please let me know if you get the solution.

Did you use aws lambda to solve the problem? I’m having the same problem, so if you find a solution, do let me know.

I have the below python code which is pretty straightforward.
What am I missing?

import hashlib
import hmac
# Secret token
secret_token = "zoom-secret-token"

# Get the plainToken from the request body
# PS: My request payload contains `plain_token` and not `plainToken`
plain_token = request.data["payload"]["plain_token"]

# Example usage:
hashed_token = hmac.new(
    key=secret_token.encode(),
    msg=plain_token.encode(),
    digestmod=hashlib.sha256,
).hexdigest()

# # Create the response JSON object
response_json = {"plainToken": plain_token, "encryptedToken": hashed_token}

@reid were you able to get this working?
We have a similar issue and have gotten nowhere with Zoom support — and no responses to our ticket.

I gave ChatGPT the instructions from Zoom’s documentation and it generated the following code for me, which did pass validation.

import json
import logging
import hashlib
import hmac
import base64

Configure logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

Replace ‘your_secret_token’ with your actual Zoom webhook secret token

SECRET_TOKEN = ‘secret-token’

def lambda_handler(event, context):
# Log the incoming event
logger.info(“Received event: %s”, json.dumps(event))

# Parse the JSON body from the incoming event
try:
    body = json.loads(event.get('body', '{}'))
except json.JSONDecodeError:
    logger.error("Error decoding JSON from the body")
    return {
        'statusCode': 400,
        'body': json.dumps({'message': 'Invalid JSON in request body'})
    }

logger.info("Parsed body: %s", body)

# Handle Zoom verification request
if body.get('event') == 'endpoint.url_validation':
    plain_token = body.get('payload', {}).get('plainToken')
    if plain_token:
        # Compute HMAC SHA-256 hash
        encoded_secret = bytes(SECRET_TOKEN, 'utf-8')
        encoded_plain_token = bytes(plain_token, 'utf-8')
        hmac_sha256 = hmac.new(encoded_secret, encoded_plain_token, hashlib.sha256)
        encrypted_token = hmac_sha256.hexdigest()

        # Create response object
        response_body = {
            'plainToken': plain_token,
            'encryptedToken': encrypted_token
        }

        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps(response_body)
        }
    else:
        logger.error("plainToken not found in the payload")
        return {
            'statusCode': 400,
            'body': json.dumps({'message': 'plainToken not found'})
        }

# Default response for any other requests
return {
    'statusCode': 200,
    'headers': {
        'Content-Type': 'application/json'
    },
    'body': json.dumps({'status': 'received'})
}