Webhooks

Webhooks

A Webhook is a way for Zoho Webinar to instantly notify an external application when a specific event occurs. The moment a trigger fires in your workflow, the Webhook sends an PUT or POST request carrying event data to a URL you specify.
In Zoho Webinar, Webhooks are configured inline within the Workflow setup, either as an Instant Action or a Scheduled Action. When you select Webhook as your action type a configuration form appears directly within the workflow where you fill in all the required details.

 How to Configure a Webhook?   

Follow the steps below to configure.   
Webhooks are part of actions in Workflow. Create a workflow to use Webhooks.
  1. Enter a Name to identify this Webhook within the workflow.
  2. The Method is set to POST by default and can be changed to PUT as per requirement.
  3. Enter the Webhook URL provided by your receiving application.
  4. Add an optional Description to note the purpose of this Webhook.
  5. Select an Authorization Type: General, Connection, or Signature. Configure the selected type as required.
  6. Add the Body in JSON format with the event data you want to send.
  7. Add Query by + Add and entering any Parameters clicking Parameter key-value pairs.
  8. Add any Headers by clicking + Add Parameter and entering key-value pairs.
  9. Click Save to apply the Webhook configuration and complete the workflow setup.

   

 Authorization Types  

Zoho Webinar provides three authorization options for securing your Webhook. Select the one that matches what your receiving application requires. 

General 

General authorization provides a basic level of authentication for your Webhook. Use this when your receiving application requires a straightforward credential to verify incoming requests.

Connection

Connection authorization links the Webhook to an existing Connection set up in the Connections module. Instead of entering credentials manually, you select a pre-authorized Connection and the Webhook uses those stored credentials to authenticate with the receiving application.

Signature 

Signature authorization uses an auto-generated secret token to sign each Webhook request. The receiving application uses this token to verify that the request genuinely came from Zoho Webinar and has not been tampered with in transit.
  • The token is automatically generated by Zoho Webinar

  • Click Copy to copy the token and add it to your receiving application

  • Click Regenerate to create a new token if the current one is compromised. Regenerating invalidates the previous token immediately. 

 Body  

The Body contains the data payload that Zoho Webinar sends to the receiving application with each Webhook request. You can send the payload in one of two formats:  

JSON

JSON format is used when the receiving application expects structured, machine-readable data. The payload is written as a JSON object with key-value pairs representing the event data you want to send.

Form Data

Form Data format is used when the receiving application expects the payload submitted as a URL-encoded string. Each value is URL-encoded and pairs are joined with &.

Validating Webinar Webhook Requests

To ensure that the webhook requests your service receives are actually coming from Zoho Webinar and were not tampered with in transit, every request sent with Signature authentication includes an `X-WEBINAR-SIGNATURE` header.
This header is an HMAC SHA-256 hash, computed using your organization's signature key (secret) combined with the raw request body. On your endpoint, recompute the hash from the request body and your secret, then compare it to the value in the header. If the two are equal, the request has passed validation. Otherwise, the request may have been tampered.  

 

Body Sent

{"webinarId":"12345","event":"registration","email":"user@example.com"}

  

Header:

X-WEBINAR-SIGNATURE: a1b2c3d4e5f6...

Content-Type: application/json

 

SAMPLE CODE:

Node.js

const crypto = require("crypto");

 

/**

* Verifies an X-WEBINAR-SIGNATURE header. Works for both JSON and

* form-data webhooks — just pass the raw request body.

*

* @param {string} rawBody         Raw HTTP request body as received (JSON text or

*                                 url-encoded form string); never parsed/re-serialized.

* @param {string} signatureHeader Value of the X-WEBINAR-SIGNATURE header.

* @param {string} secret          The org's webhook signature key (token).

* @returns {boolean}              True if the signature is valid.

*/

function verifySignature(rawBody, signatureHeader, secret) {

  if (!signatureHeader || !secret) {

    return false;

  }

 

  const body = rawBody || "";

  const expected = crypto

    .createHmac("sha256", secret)

    .update(body, "utf8")

    .digest("hex"); // lowercase hex

 

  const expectedBuf = Buffer.from(expected, "utf8");

  const receivedBuf = Buffer.from(signatureHeader.trim().toLowerCase(), "utf8");

 

  // Lengths must match for timingSafeEqual

  if (expectedBuf.length !== receivedBuf.length) {

    return false;

  }

  return crypto.timingSafeEqual(expectedBuf, receivedBuf);

}

 

// Example usage

const secret = "your-webhook-signature-token";

 

// JSON webhook: rawBody is the JSON text as received

const jsonBody = '{"name":"Ann","id":42}';

 

// Form-data webhook: rawBody is the url-encoded form string as received

const formBody = "name=Ann&note=hi+there";

 

const signatureFromHeader = "..."; // X-WEBINAR-SIGNATURE value

 

const isValid = verifySignature(jsonBody, signatureFromHeader, secret);

console.log(isValid ? "Signature is valid!" : "Signature is invalid!");

Python 

import hmac

import hashlib

 

 

def verify_signature(raw_body: str, signature_header: str, secret: str) -> bool:

    """

    Verifies an X-WEBINAR-SIGNATURE header. Works for both JSON and

    form-data webhooks -- just pass the raw request body.

 

    :param raw_body:         Raw HTTP request body as received (JSON text or

                             url-encoded form string); never parsed/re-serialized.

    :param signature_header: Value of the X-WEBINAR-SIGNATURE header.

    :param secret:           The org's webhook signature key (token).

    :return:                 True if the signature is valid.

    """

    if not signature_header or not secret:

        return False

 

    body = raw_body or ""

    expected = hmac.new(

        secret.encode("utf-8"),

        body.encode("utf-8"),

        hashlib.sha256,

    ).hexdigest()  # lowercase hex

 

    # Constant-time comparison (header normalized to lowercase)

    return hmac.compare_digest(expected, signature_header.strip().lower())

 

 

# Example usage

if __name__ == "__main__":

    secret = "your-webhook-signature-token"

 

    # JSON webhook: raw_body is the JSON text as received

    json_body = '{"name":"Ann","id":42}'

 

    # Form-data webhook: raw_body is the url-encoded form string as received

    form_body = "name=Ann&note=hi+there"

 

    signature_from_header = "..."  # X-WEBINAR-SIGNATURE value

 

    is_valid = verify_signature(json_body, signature_from_header, secret)

print("Signature is valid!" if is_valid else "Signature is invalid!")

Java 

import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;

import java.nio.charset.StandardCharsets;

import java.security.MessageDigest;

 

public class WebinarSignatureValidator {

 

    /**

     * Verifies an X-WEBINAR-SIGNATURE header. Works for both JSON and

     * form-data webhooks — just pass the raw request body.

     *

     * @param rawBody         the raw HTTP request body as received (JSON text or

     *                        url-encoded form string); never parsed/re-serialized

     * @param signatureHeader value of the X-WEBINAR-SIGNATURE header

     * @param secret          the org's webhook signature key (token)

     * @return true if the signature is valid

     */

    public static boolean verifySignature(String rawBody, String signatureHeader, String secret) {

        if (signatureHeader == null || signatureHeader.isEmpty() || secret == null) {

            return false;

        }

        try {

            String body = rawBody != null ? rawBody : "";

            Mac mac = Mac.getInstance("HmacSHA256");

            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));

            byte[] raw = mac.doFinal(body.getBytes(StandardCharsets.UTF_8));

 

            StringBuilder hex = new StringBuilder(raw.length * 2);

            for (byte b : raw) {

                hex.append(Character.forDigit((b >>> 4) & 0xF, 16));

                hex.append(Character.forDigit(b & 0xF, 16));

            }

            String expected = hex.toString();

 

            // Constant-time comparison (header normalized to lowercase)

            return MessageDigest.isEqual(

                    expected.getBytes(StandardCharsets.UTF_8),

                    signatureHeader.trim().toLowerCase().getBytes(StandardCharsets.UTF_8));

        } catch (Exception e) {

            return false;

        }

    }

 

    // Example usage

    public static void main(String[] args) {

        String secret = "your-webhook-signature-token";

 

        // JSON webhook: rawBody is the JSON text as received

        String jsonBody = "{\"name\":\"Ann\",\"id\":42}";

 

        // Form-data webhook: rawBody is the url-encoded form string as received

        String formBody = "name=Ann&note=hi+there";

 

        String signatureFromHeader = "..."; // X-WEBINAR-SIGNATURE value

 

        boolean valid = verifySignature(jsonBody, signatureFromHeader, secret);

        System.out.println(valid ? "Signature is valid!" : "Signature is invalid!");

    }

}