How to Measure Your Meetings Using Whereby’s Webhooks

Learn how to use webhooks for Whereby Embedded to keep track of things like participant attendance and meeting length.

If you're using Whereby Embedded as a virtual classroom or telehealth platform, you might want to measure things such as student attendance or appointment length. Whereby lets you do both using webhooks. This post introduces you to webhooks in Whereby, and shows you how to use webhooks to keep track of participant attendance.

What is a webhook?

A webhook is an HTTP request that sends data from a source application to a destination URL, when an event occurs in the source application. The destination URL is known as the endpoint URL. With Whereby's webhooks, you provide an endpoint URL and indicate which event or events should trigger a request to it. You're in full control of the endpoints, which means that you can collect and process the data that Whereby sends in ways that suit your needs.

What you'll need

If you'd like to follow along with this guide, you'll need a two things:

  • A Whereby Embedded account with administrative privileges.

  • A web server and, optionally, an ngrok account

For development purposes, a local web server is the quickest way to get started. Some programming languages, such as PHP, have a built-in server that's well-suited to experimentation. That's what's used here.

Create a PHP server instance using the command below. You can replace :8000 with the port number of your choice, as long as that port isn't restricted by your operating system, or used by another application.

php -S localhost:8000

You'll also need to make your local server available to receive external requests. ngrok is the easiest way to do this. It's a reverse proxy and tunneling service with free and paid plans. As a bonus, ngrok uses HTTPS for its URLs, which you'll need for your webhook endpoint URLs.

Setting up your local end point

When a particular event occurs, Whereby sends a POST request to the endpoint you've specified. Information about the event gets sent as a JSON-formatted string in the request body.

In the example below, our endpoint is a small PHP application that prints the JSON it's received from the webhook. Save this as incoming/index.php in your local web server's root directory. This will be your webhook endpoint.


$request_body = file_get_contents( 'php://input' );

$json = json_decode( $request_body );

print '<pre>';
print_r( $json );
print '</pre>';

You can use a separate URL for each webhook event, or one URL to track all events.

Add a webhook endpoint to your account

Log in to your Whereby Dashboard. You'll find the Webhooks configuration settings at the bottom of the Configure page. Enter the full URL in the Endpoint URL field (e.g., and select the events for which you'd like to receive notifications.

Whereby lets you observe up to four events.

  • room.client.joined: Sent when a participant joins a meeting.

  • room.client.left: Sent when a participant leaves a room, either by using the

    Leave Room

    button or by closing the browser tab.

  • room.session.started: Triggered when there are two or more participants in a room.

  • room.session.ended: Triggered when a session ends. A session ends when there are fewer than two people in a room for one minute.

Each event object contains a type property that indicates the type of event, and a data property that contains information about the room that dispatched the event. Here's an example of a room.client.joined event object.

    "apiVersion": "1.0",
    "id": "8d1fe2e079cd44e3c0c7292c12bbbc0d934885822701b548aedd2bbf16956aa7",
    "createdAt": "2022-08-21T21:35:35.736Z",
    "type": "room.client.joined",
    "data": {
        "roomName": "/r6y7hw",
        "meetingId": "000000001",
        "roleName": "granted_visitor",
        "numClients": 2,
        "numClientsByRoleName": {
            "owner": 1,
            "granted_visitor": 1
        "metadata": null

Every event object includes a data.roomName and data.meetingId property. The room.client.joined and room.client.left events include three additional properties:

  • data.roleName, which indicates the type of participant — owner, visitor, granted_visitor, and granted_viewer — that entered or left the meeting room;

  • data.numClients, which indicates the current number of room participants; and

  • data.numClientsByRoleName, which indicates the current number of participants for each role.

The room.client.joined and room.client.left event objects also include a data.metadata field. This field reflects the value of the metadata URL parameter.

Sending custom metadata to your webhook

You can use the metadata URL parameter to send additional information about the meeting room or its participants to your webhook endpoint. For example, you can include a participant identifier to track meeting attendance.


When this meeting attendee joins using the URL above, the value of data.metadata will be abcdef123456, as shown in the example below.

    "apiVersion": "1.0",
    "id": "9c6cce3a85aed431cff9b9be861d31384b3f5e88743cf30ee4b609205e5097fc",
    "createdAt": "2022-08-21T21:38:35.736Z",
    "type": "room.client.joined",
    "data": {
        "roomName": "/r6y7hw",
        "meetingId": "000000001",
        "roleName": "granted_viewer",
        "numClients": 2,
        "numClientsByRoleName": {
            "visitor": 1,
            "granted_viewer": 1
        "metadata": "abcdef123456"

The metadata parameter value must be a string. You can use a JSON-formatted or comma-separated string as its value. Make sure that it's properly encoded for use in URLs.

Since there is a risk that a user might tamper with this value, it's a good idea to include a timestamp and signature as part of the metadata string. For example: ?metadata=abcdef123456,<timestamp>,<hashed string> You can use the timestamp and hash to validate the metadata once your application receives it.

Validating events

Each webhook request includes a Whereby-Signature header. Use this header to validate that the webhook request originated with Whereby and prevent man-in-the-middle attacks. To validate an event signature, you'll first need your webhook secret token. You can find it in the dashboard, under the Existing webhooks menu.

Next, you'll need to perform the following steps, as outlined in the developer documentation.

  1. Extract the timestamp and signature from the header. Split the string on the comma, then remove the

    t= and v1= from each string.

  2. Generate the signed payload by concatenating the timestamp, a . character, and the request body.

  3. Calculate the HMAC of the signed payload, using your webhook secret as the key.

  4. Once you've calculated the HMAC, compare its value to the signature included with the

    Whereby-Signature header. It's the portion of the string that follows v1=. Use a timing attack safe comparison function, as shown here, instead of a standard equality operator.

  5. Guard against replay attacks by determining the difference between the current time and the header's timestamp value and comparing it to your allowed threshold (MAX_ELAPSED_TIME).

The documentation includes an example using Node.js and JavaScript. The example below uses PHP.


# Store this in a secrets manager, not on the file system!
$webhook_secret  = 'this_is_not_a_good_secret_93823xf';

$request_headers = getallheaders();
$request_body    = file_get_contents( 'php://input' );

function is_valid_webhook_event( $body, $headers, $secret, $max_diff = 60 ) {
  $is_valid = false;

  Split the value of the Whereby-Signature header on the comma
  This will create an array in which the timestamp is the first item
  and the signature is the second.
  $signature = explode( ',', $headers['Whereby-Signature'] );

  # Strip the t= and v1= from each string.
  $signature = array_map(function( $item ) {
    return preg_replace( '/^[tv][0-9]{0,1}=/', '', $item );
  }, $signature );

   Only continue if the JSON is valid.
   json_decode( $body ) will be null / falsy if it is not.

   In a production-ready application, you may want to validate the schema
   as well.
  if( json_decode( $body ) ):

		 Concatenate the timestamp, a . character, and the response 
     body to create a signed payload
    $signed_payload = $signature[0] . '.' . $body;

    # Calculate the HMAC
    $hmac_hash = hash_hmac( 'sha256', $signed_payload, $secret );

    # Compare the HMAC and header signature.
    $hash_is_equal = hash_equals($hmac_hash, $signature[1] );

     Calculate the difference between the current time and timestamp.
     Prevents replay attacks
    $time_diff = ( time() - intval( $signature[0] ) );

    $is_valid = $hash_is_equal && $time_diff < $max_diff;


  return $is_valid;

if( is_valid_webhook_event( $request_body, $request_headers, $webhook_secret ) ):
  // Parse and process the incoming JSON.

You should validate incoming events before doing anything with the event data. Be sure to validate, filter, or escape its schema and values as appropriate for your application, too.

Webhooks can help you build more robust applications using Whereby Embedded. They're a great way to measure how employees and customers are using your organization's rooms.

Whereby makes it easy to create virtual meetings and classrooms. Create an account for free. No credit card is required to join, and you get 2,000 participant minutes every month.

Other articles you might like