Webhook verification failed due to special characters in payload

We have recently integrated Zoom WebHook validation process at our application end.
We followed the process mentioned in official documentation :

Verify Zoom Web Hooks

Everything worked fine & we are able to successfully validate the calls received from Zoom Webhook.

But signature validation fails in very specific scenarios - in case of meeting related events :

Our Observation during failure :

  • Meeting contains external participants (outside our company)
  • Those external participants have special characters like emojis in their name,
    e.g. Elie Guez - ChoYou :flamingo:, Ahad :notes: etc.
  • External participant’s email is missing from payload

Please help.

1 Like

Hi @rahul.bhooteshwar
Thanks for reaching out to the Zoom Developer Forum, I am happy to help here!
So, let me just confirm if I am understanding correctly.

You are generating the signature to validate the webhook correctly (with the Zoom secret token), but this signature is invalid when these 3 scenarios are in place:

  • Meeting contains external participants (outside our company)
  • Those external participants have special characters like emojis in their name,
    e.g. Elie Guez - ChoYou :flamingo:, Ahad :notes: etc.
  • External participant’s email is missing from payload

Are you generating the signature like this:

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
}

@elisa.zoom yes that’s correct.
I am using exactly same code for verification.
It works fine all the times except the scenario mentioned.

I don’t know if all those 3 conditions are responsible for the failure or just one of those (most probably user name with special characters).

I guess it is not applicable for our internal users as their names are proper alphabetical words & contains no emojis. But external meeting participants have their choice of names (containing these emojis).

Please let me know if anything else is needed from my side.

This is a critical issue for our integrations. Please help.

Thanks for confirming this @rahul.bhooteshwar

Allow me some time to do some testing on my end and I will come back to you with an update shortly.
Best,
Elisa

Hello @elisa.zoom ,
Did you get a chance to test and confirm this behaviour?

Hi @rahul.bhooteshwar
Thank you for your patience while I troubleshoot this on my end
Unfortunately, I have not been able to replicate this behavior.
I will go ahead and send you a private message so we can exchange more information

Best,
Elisa

@elisa.zoom
We have the same situation here.
Please could you tell me all the logic for encoding?
We use Python.

how to reproduce

  • prepare 2 devices
    • one is logged in (device_1)
    • the other is not logged in (device_2)
    • I’m using iPad Zoom App for device_2
  • start a meeting using device_1
  • join the meeting using device_2
    • tap on “Join Meeting”
    • type in Meeting ID (10 digits of numbers)
    • change the display name like “Masahiro’s iPadđŸ’»â€
    • tap on “Join”
  • “meeting.participant_joined” webhook’s secret_token validation fails

failing webhook’s request body
I’ve masked certain parameters.
Validation succeeds if the user_name does not contain emoji.

{
    "payload": {
        "account_id": "xxxxxxxxxxxxxxxxxxxxxx",
        "object": {
            "duration": 0,
            "start_time": "2023-02-21T04:27:43Z",
            "timezone": "Asia/Tokyo",
            "topic": "Masahiro Yamamotoăźăƒ‘ăƒŒă‚œăƒŠăƒ«ăƒŸăƒŒăƒ†ă‚Łăƒłă‚°ăƒ«ăƒŒăƒ ",
            "id": "0000000000",
            "type": 4,
            "uuid": "IMDA4t/JQ9aZ0qjsAssdqg==",
            "participant": {
                "user_id": "00000000",
                "user_name": "ć±±æœŹæ˜ŒćŒ˜ăźiPadđŸ’»",
                "id": "",
                "join_time": "2023-02-21T04:28:26Z",
                "email": "",
                "participant_uuid": "xxx-xxxxxx-xxxxxxxxxxx"
            },
            "host_id": "xxxxxxxxxxxxxxxxxxxxxx"
        }
    },
    "event_ts": 1676953710373,
    "event": "meeting.participant_joined"
}

The python code we use for verification (Only the encoding part)
We use “ensure_ascii=False” in json.dumps to encode kanji and kana characters correctly

import hmac
import json
from hashlib import sha256

json_request = json.dumps(request.body, separators=(',', ':'), ensure_ascii=False)
message = f"v0:{timestamp}:{json_request}"
hashed_message = f"v0={hmac.new(secret_token.encode(), message.encode(), sha256).hexdigest()}"

Hello, @elisa.zoom
Could you help me with this?

Hi @tech-zoom_zp
Thanks for reaching out to the Zoom Developer Forum, I am happy to help here! I have created an internal ticket (ZOOM- 506645) about this issue and will update you shortly
Bes,t
Elisa

1 Like

Hi @elisa.zoom
It’s been a long while without any further information.
Should I ask at some other place?

Hope you could help me before October.

I am sorry I did not get back to you!
I will take a look at the internal ticket to see if there was any movement and will come back to you

1 Like

@elisa.zoom

Hi. I haven’t gotten any answer yet. I had presumed that a solution you private mailed to Mr. Rahul Bhooteshwar could be used for our case, too. Is the situation so complicated?
It’s stressful getting reminder emails to update webhook verification and always reminding the deadline to myself. Once I have the answer and be done with it, I can forget about the deadline altogether.

Hi @tech-zoom_zp
I totally understand your point.
I did not update the thread since I do not see any movement on the internal ticket I created.
I just re-engaged with the team and will push to get an answer soon.
Also, the dates for deprecation has been pushed to Februrary next year

1 Like

Hi again @tech-zoom_zp
I heard back from our Engineering team and it looks like when the emoji is a Unicode string we support it’s validation. In the case of this emoji :computer:, my team was able to test and able to verify the webhook.

@elisa.zoom

Thanks for reply!

I tried on my end again, but un-successful.
Could you tell me what is wrong with my python code? It fails only when the request body includes an Unicode emoji.

The python code I use for verification.

import hmac
import json
from hashlib import sha256

class HasVerifiedZoomSignature(BasePermission):
    def has_permission(self, request, view):
        secret_token = settings.ZOOM["SECRET_TOKEN"]
        timestamp = request.headers.get("x-zm-request-timestamp")
        signature = request.headers.get("x-zm-signature")

        json_request = json.dumps(request.data, separators=(",", ":"), ensure_ascii=False)
        message = f"v0:{timestamp}:{json_request}"
        hashed_message = f"v0={hmac.new(secret_token.encode(), message.encode(), sha256).hexdigest()}"

        result = signature == hashed_message
        return result

Edit: I believe the case is the same as this one (the solution is not explained in the thread, though).

1 Like

Same here. I’m getting signature mismatch error when data has a Spanish tilde accent mark in it. But in all other cases the signature matches. Here’s my Python code:

        x_zm_signature = headers['X-Zm-Signature']
        x_zm_request_timestamp = request.headers['X-Zm-Request-Timestamp']

        data = request.get_json()

        message = "v0:{}:{}".format(x_zm_request_timestamp, json.dumps(data, separators=(',', ':'))).encode('utf-8')

        hashed_message = hmac.new(secret_token, msg=message, digestmod=hashlib.sha256).hexdigest()

        signature = "v0={}".format(hashed_message)

        if x_zm_signature != signature:
            ......

Given that @ymnoor21 reports accent mark (as opposed to emoji), the difference probably lies in either of:

  • unicode normal form (composed vs decomposed), or
  • escaping JSON content

Ideally the hash should be computed over the raw request body, as opposed to parsed request JSON.
However that assumes Zoom computing the hash over exact request body that’s sent out.

Perhaps Zoom folks could share the snippet of the code on their side, incl. runtime (python/nodejs/golang/etc.) and library (requests?httpx?fetch api?) versions? That would help.

With a bunch of smart developers in this forum, I’m pretty sure this can be solved really fast with just a bit of raw data :bowing_man:

2 Likes