DEV Community

Elisa Levet for Zoom

Posted on • Updated on

Using ngrok to receive Zoom Events

Zoom utilizes webhooks as a medium to notify third-party applications about events that occur in a Zoom account.

So, instead of making repeated calls to pull data from the Zoom API, you can use webhooks to get information on events that happen in a Zoom account.

This guide covers how to implement Zoom webhooks using ngrok as our secure URL to receive events from our Zoom account.

We will use our Webhook-sample-node.js app available on Gitbhub and we will be setting up a sample app in the Zoom Marketplace.

Step 1: Setting up our local environment

  • Install the sample app by running the following commands in your terminal:
git clone https://github.com/zoom/webhook-sample-node.js.git
cd webhook-sample-node.js
npm install
Enter fullscreen mode Exit fullscreen mode
  • Create a .env file by running the following command in your terminal:
touch .env
Enter fullscreen mode Exit fullscreen mode

And add the following placeholder

ZOOM_WEBHOOK_SECRET_TOKEN= 
Enter fullscreen mode Exit fullscreen mode

Note: Do not run your app quite yet, we need to head to the Zoom Marketplace and create an app first!

Step 2: Create an app in the Marketplace.

For this tutorial will use a Webhook only app.

  1. Log in to your Zoom account and head to the Zoom Marketplace
  2. Click the dropdown that says Develop and then click Build App
  3. Select the Webhook Only app and give your app a name
  4. Make sure to fill up the required Basic information (Company name, your name, email address)
  5. On the Feature tab, copy the Secret Token and paste it in your .env file, under the ZOOM_WEBHOOK_SECRET_TOKEN placeholder; this value will be used later to validate our endpoint URL and to start receiving event notifications.
  6. Click the Event subscription slider and click on Add Event Subscritions, give your subscription a name; and Add events to your app to subscribe, for example:
    • Meeting has been created
    • Meeting has been updated
    • Meeting has been deleted.

Note: for now, we will be skipping the Event Notification Endpoint URL Validation, but we will come back to this later in the guide.

Step 3: Running our sample app and ngrok

Make sure you’ve saved your project after copying the value of the Secret Token from our newly created Webhook Only sample app, into the .env file.

Now you can run your app with the following command in your terminal:

npm run start
Enter fullscreen mode Exit fullscreen mode

Now that your app is running locally on port 4000, we need to expose our local server to the internet to accept post requests, we will be using ngrok for this. (If you are not an ngrok user yet, just sign up for ngrok for free and follow the steps to install ngork and connect your account)

Start your ngrok by running the following command in your terminal:

ngrok http 4000
Enter fullscreen mode Exit fullscreen mode

Copy the ngrok https url displayed in the terminal and paste it in your Event notification url input, followed by the /webhook path, (it should look like this https://exampleurl.ngrok.io/webhook)

Step 4: Validate your endpoint URL

Once you have added your ngrok https url followed by /webhook path, we will have to validate our endpoint.

  1. Click on the Validate button and expect to get a “Validated” message.
  2. Click on Save and then Continue to make sure that your app is activated on the account and that it’s ready to receive events from Zoom.

Screenshot from Marketplace validation

What happened here is:
Zoom requires to manually trigger the webhook validation when you add a new event or make changes to an existing one.

Zoom uses a challenge-response check (CRC) for webhook validation and when you click the Validate button in your app, a CRC occurs and Zoom will make a POST request to your https endpoint with a challenge-request body and your app will need to response with the challenge response and a 200 or 204 HTTP response code, within 3 seconds

This is how the CRC event sent by Zoom looks like:

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

Enter fullscreen mode Exit fullscreen mode

Our sample app, will be listening to the event ‘ednpoint.url_validation’ and once it receives it, it we will create a HMAC SHA-256 hash using the Zoom webhook secret token and the plainToken received in the CRC request body; to lastly respond to Zoom with a Challenge Response and 200 status

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

  response.status(200)
  response.json({
    "plainToken": request.body.payload.plainToken,
    "encryptedToken": hashForValidate
  })
}
Enter fullscreen mode Exit fullscreen mode

Challenge response body example:

{
  "plainToken": "qgg8vlvZRS6UYooatFL8Aw",
  "encryptedToken": "23a89b634c017e5364a15599e2bb04bb1558d9c3a121faa5"
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Receiving events in our sample app

Head to your Zoom account and trigger the respective Webhook, in this case as we chose the Meeting has been created event, you will have to schedule a Zoom meeting to be able to receive the event notification.

Once you create a meeting, you will see the Webhook headers and payload logged in terminal, something like this:

Request headers

{
   "host":"db05-2603-7000-8f03-fe4c-b56e-6f3b-bded-7b6d.ngrok.io",
   "user-agent":"Zoom Marketplace/1.0a",
   "content-length":"564",
   "authorization":"{LEGACY_WEBHOOK_VERIFICATION_TOKEN}",
   "clientid":"{clientID}",
   "content-type":"application/json; charset=utf-8",
   "x-forwarded-for":"{X_FORWARDED_FOR}",
   "x-forwarded-proto":"https",
   "x-zm-request-timestamp":"{X_ZM_REQUEST_TIMESTAMP}",
   "x-zm-signature":"{X_ZM_SIGNATURE}",
   "x-zm-trackingid":"{X_ZM_TRACKINGID}",
   "Accept-encoding":"gzip"
}
Enter fullscreen mode Exit fullscreen mode

Response body

{
   "event":"meeting.created",
   "payload":{
      "account_id":"{ACCOUNT_ID}",
      "operator":"{EMAIL}",
      "operator_id":"{OPERATOR_ID}",
      "object":{
         "uuid":"{MEETING_UUID}",
         "id":"{MEETING_ID}",
         "host_id":"{HOST_ID",
         "topic":"My Meeting",
         "type":2,
         "start_time":"2023-01-26T21:00:00Z",
         "duration":60,
         "timezone":"America/New_York",
         "join_url":"{JOIN_URL}",
         "password":"{PASSWORD}",
         "settings":[
            "Object"
         ],
         "is_simulive":false
      }
   },
   "event_ts":1674764224723
}
Enter fullscreen mode Exit fullscreen mode

What happened here is:

After receiving the webhook event, the sample app constructed a message string with “v0”, the webhook request header “x-zm-request-timestamp” value and the webhook request body

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

Once the app constructed the message, it created a hash message, by setting the webhook’s secret token as the secret/salt and the message as the string to hash and outputed in hex format

const hashForVerify = crypto.createHmac('sha256', ZOOM_WEBHOOK_SECRET_TOKEN).update(message).digest('hex')
Enter fullscreen mode Exit fullscreen mode

Created a signature using the hashed Message by prepending "v0=" to it:

const signature = `v0=${hashForVerify}`
Enter fullscreen mode Exit fullscreen mode

Compared the signature that we created with the value from our request header “x-zm-signature”

If the value of signature matches with the value of the request header “x-zm-signature”, we will start receiving events.

You should now have a running app enabled to receive events in a secure URL.

It is important to remember that every time that you make changes your App in the Marketplace (adding new events or restarting our ngrok secure tunnel) you will have to validate the endpoint URL again.

Thank you for follow along and happy coding! 🙂

Top comments (0)