Subscribing to events

You can subscribe to receive real-time notifications from Chatlayer whenever certain events occour. Those notifications are delivered to you via webhook calls.

At the moment we support the following event types

  • Message events ( MESSAGE_EVENT )
  • Tracking events ( TRACKING_EVENT )

Follow this guide to add a Tracking event step to your bot flow and start generation custom tracking events. Adding a Tracking event step to your bot will allow you to create custom dashboards and funnels to assist in the analysis of your bot performance.

Before subscribing, you must create a route in a service own by you that will receive and process the events. Your service must be publicly accesible over the internet, use SSL and listen on port 443. The following table lists the types of notifications your handler must process. The X-Message-Type request header contains the notification’s type.

Notification Type Description
webhookcallbackverification Contains the challenge used to prove that you own the event handler. This is the first event you’ll receive after subscribing to an event. See Responding to a challenge request.
notification Contains the event’s data. See Processing an event.
revocation Contains the reason why Chatlayer revoked your subscription. See Revoking your subscription.

You must respond to notification requests within a few seconds. If your server takes too long to respond, the request will time out. If you fail to respond quickly enough too many times, the subscription’s status changes to notification_failures_exceeded and Chatlayer will revoke the subscription. If your server can’t process a notification request quickly enough, consider writing the event to temporary storage and processing the notification after responding with 2XX.

Processing an event

When an event that you subscribed to occurs, Chatlayer sends your event handler a notification message that contains the event’s data. Notification messages set the X-Message-Type header to notification. For example:

Copy
Copied
X-Message-Type: notification

Your response should return a 2XX status code.

The following snippet shows a simple JavaScript implementation that responds to an event notification request:

Copy
Copied
// Notification request headers
const MESSAGE_TYPE = "X-Message-Type".toLowerCase();

// Notification message types
const MESSAGE_TYPE_NOTIFICATION = "notification";

// Get JSON object from body, so you can process the message.
let notification = JSON.parse(req.body);

if (MESSAGE_TYPE_NOTIFICATION === req.headers[MESSAGE_TYPE]) {
  // TODO: Do something with event's data.
  console.log(`Event type: ${notification.subscription.type}`);
  console.log(JSON.stringify(notification.event, null, 4));

  res.sendStatus(204);
}

Responding to a challenge request

When you subscribe to an event, Chatlayer sends you a verification challenge to make sure that you own the event handler specified in the request. Challenge messages set the X-Message-Type header to webhook_callback_verification. For example:

Copy
Copied
X-Message-Type: webhook_callback_verification

The challenge field in the body of the request contains the challenge value that you must respond with.

Copy
Copied
{
  "challenge": "applebanana",
  "subscription": {
    "id": "437ae806-8094-4d12-b79a-89950d84615c",
    "status": "webhook_callback_verification_pending",
    "created_at": "2024-10-22T11:49:22.128Z"
  }
}

Your response must return a 200 status code, the response body must contain the raw challenge value, and you must set the Content-Type response header to the length of the challenge value. If successful, your subscription is enabled.

Simple Node.js example

The following is a simple event handler that uses Node.js and Express.

To run the example, you must have Node.js installed.

  1. Open a terminal window
  2. Create a folder for the example and cd to it
  3. Run npm init
  4. Run npm install express --save
  5. Create a file and name it app.js
  6. Copy the example to app.js
  7. Update the getSecret() function to get the secret key that you use when you subscribe to events
  8. Run node app.js
Copy
Copied
const crypto = require("crypto");
const express = require("express");
const app = express();
const port = 8080;

// Notification request headers
const MESSAGE_ID = "X-Message-Id".toLowerCase();
const MESSAGE_TIMESTAMP = "X-Message-Timestamp".toLowerCase();
const MESSAGE_SIGNATURE = "X-Message-Signature".toLowerCase();
const MESSAGE_TYPE = "X-Message-Type".toLowerCase();

// Notification message types
const MESSAGE_TYPE_VERIFICATION = "webhook_callback_verification";
const MESSAGE_TYPE_NOTIFICATION = "notification";
const MESSAGE_TYPE_REVOCATION = "revocation";

// Prepend this string to the HMAC that's created from the message
const HMAC_PREFIX = "sha256=";

app.use(
  express.raw({
    // Need raw message body for signature verification
    type: "application/json",
  })
);

app.post("/eventsub", (req, res) => {
  let secret = getSecret();
  let message = getHmacMessage(req);
  let hmac = HMAC_PREFIX + getHmac(secret, message); // Signature to compare

  if (true === verifyMessage(hmac, req.headers[MESSAGE_SIGNATURE])) {
    console.log("signatures match");

    // Get JSON object from body, so you can process the message.
    let notification = JSON.parse(req.body);

    if (MESSAGE_TYPE_NOTIFICATION === req.headers[MESSAGE_TYPE]) {
      // TODO: Do something with the event's data.

      console.log(`Event type: ${notification.subscription.type}`);
      console.log(JSON.stringify(notification.event, null, 4));

      res.sendStatus(204);
    } else if (MESSAGE_TYPE_VERIFICATION === req.headers[MESSAGE_TYPE]) {
      res
        .set("Content-Type", "text/plain")
        .status(200)
        .send(notification.challenge);
    } else if (MESSAGE_TYPE_REVOCATION === req.headers[MESSAGE_TYPE]) {
      res.sendStatus(204);

      console.log(`${notification.subscription.type} notifications revoked!`);
      console.log(`reason: ${notification.subscription.status}`);
      console.log(
        `condition: ${JSON.stringify(
          notification.subscription.condition,
          null,
          4
        )}`
      );
    } else {
      res.sendStatus(204);
      console.log(`Unknown message type: ${req.headers[MESSAGE_TYPE]}`);
    }
  } else {
    console.log("403"); // Signatures didn't match.
    res.sendStatus(403);
  }
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

function getSecret() {
  // TODO: Get secret from secure storage. This is the secret you pass
  // when you subscribed to the event.
  return "your secret goes here";
}

// Build the message used to get the HMAC.
function getHmacMessage(request) {
  return (
    request.headers[MESSAGE_ID] +
    request.headers[MESSAGE_TIMESTAMP] +
    request.body
  );
}

// Get the HMAC.
function getHmac(secret, message) {
  return crypto.createHmac("sha256", secret).update(message).digest("hex");
}

// Verify whether our hash matches the hash that Chatlayer passed in the header.
function verifyMessage(hmac, verifySignature) {
  return crypto.timingSafeEqual(
    Buffer.from(hmac),
    Buffer.from(verifySignature)
  );
}
We'd love to hear from you!
Rate this content:
Still have a question?
 
Ask the community.