Unable to validate zoom webhook through api gateway

Hi everyone,

I’m running into an issue with Zoom webhook validation and I’m hoping someone can clarify what’s happening.

  • When I expose my service using ngrok, Zoom’s webhook URL validation (endpoint.url_validation) works fine.

  • But when I put the same service behind my API Gateway (Express.js gateway forwarding requests to microservices), Zoom responds with “validation failed”.

Here’s some context:

  • My Sessions service has express.json() enabled globally, and I can’t disable it for just this route.

  • For webhook signature validation, I’m building the message like this:

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

  • This works with ngrok directly, but fails when the request goes through my gateway.

My guess: the gateway or express.json() is modifying the body (parsing and re-stringifying), so the payload is no longer byte-for-byte identical to what Zoom sent. This would explain why signature validation fails.

Questions:

  1. Is my understanding correct that Zoom requires the exact raw request body for signature and URL validation?

  2. If so, what’s the best practice for handling this in an Express.js microservices setup where express.json() is already in use globally?

    • Should I capture the raw body using the verify option in express.json()?

    • Or should I route Zoom’s webhook through express.raw({ type: "application/json" }) at the gateway and then forward it untouched?

Any guidance or confirmation would be really helpful. Thanks!

Based on this I believe you may be mixing up webhook verification and webhook validation. Webhook validation involves implementing the challenge-response check flow which includes hashing the plainToken sent by Zoom, creating the response JSON object and responding to Zoom servers within 3 seconds with the JSON object:

Webhook endpoint validation (endpoint.url_validation event)

  • Happens once when you register your webhook.

  • You must echo back Zoom’s plainToken as an encryptedToken.

  • This does not involve HMAC verification or raw body concerns.

  • If validation fails, it usually means the app didn’t return the JSON response in the exact format Zoom expects.

Webhook request verification (all subsequent events)

  • Uses the x-zm-signature header.
  • This does depend on the raw body being byte-for-byte identical, which is where express.json() parsing could be breaking things via your API Gateway if your issue is actually verification.

After reviewing the linked documentation above, can you clarify whether verification or validation is your issue?