Webhook verification not well defined

For all web hook events, to verify that the event came from Zoom, consuming applications need to create a message containing the request body and hash it using SHA-256. In order to ensure the message produced by the application is the same as that produced by Zoom, this requires that the JSON request body maintains keys in the same order. But JSON makes no guarantees about key ordering, and JSON serialization / deserialization libraries are free to change key ordering as they please according the JSON specification. So, it’s possible for an application to produce an encrypted message that does not match that produced by Zoom while following Zoom’s specifications to a tee.

Was this considered when designing the API?

This can cause issues such as the following. Consider the case where there is middleware between Zoom producing the webhook and an application consuming the webhook. During serialization/deserialization, the ordering of keys in the json request body can be changed. And now the application has no way of verifying that the request indeed came from Zoom.

Moreover, Zoom events do not have a consistent key ordering. Sometimes payload is the first field, sometimes it is event, etc…

So there is no way to accurately convert the json event to a thrift struct or protobuf without messing up key ordering for some event when converting back to JSON

Hi @peterzoom
Thanks for reaching out to the Zoom Developer Forum!
And thank you for looking into this issue and more important for bringing this up to us.
I will look into this issue and will come back to you with an update shortly.
In my experience, I normally receive the events in the same order but if this is not the case for you, could you please provide a couple of examples for this?

Also have you looked into our Docs, we have an example in Node.js on how to generate the message:

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

Let me know if this helps,