Handling Events

1. Handling Events

The CleanSpeak webhook system is designed to provide feedback to your servers when certain actions are taken inside of CleanSpeak. All events are sent from CleanSpeak to the webhooks you configure via the Management Interface. These events are sent via an HTTP POST using application/json data. This is similar to the webservice API except in reverse. Instead of you calling an API, the API calls your system with data as it happens.

There are five types of events that CleanSpeak will send:

  • Content Approvals - when a moderator approves or rejects content from the pre-approval queue

  • User Actions - when a moderator takes an action on a user

  • Content Edits - when a moderator edits a piece of persistent content

  • Content Deletes - when a moderator deletes a piece of persistent content

  • Filter Approvals - when an admin or filter manager approves pending filter changes

Additionally, an Event Handler is created in your application to process these event types.

Each of the event types are covered in detail below:

2. Content Approvals

The Pre-Approval Queue supports the approval and rejection of content. Moderators login to the CleanSpeak Management Interface and from there can approve and reject content. Once moderators have approved or rejected content, CleanSpeak will send events back into your system using the JSON format described below.

2.1. Request

Table 1. Request Body

type [String]

This parameter specifies the type of event and will always be contentApproval for this type of event.

approvals [Object]

This object contains the approvals performed by a moderator.

The format of the object is 1-N key-value pairs, the key is the UUID of the content item, the value is the action performed ("approved", "rejected").

moderatorEmail [String]

This parameter specifies the email of the moderator that performed the action that resulted in the event being sent.

moderatorExternalId [String]

This parameter specifies the external id (if one is configured) of the moderator that performed the action that resulted in the event being sent.

moderatorId [UUID]

This parameter specifies the id of the moderator that performed the action that resulted in the event being sent.

Example Request JSON
{
  "type": "contentApproval",
  "approvals": {
    "8207bc26-f048-478d-8945-84f236cb5637": "approved",
    "86d9e3e1-5752-41dc-aa55-2a832728ec33": "approved",
    "a1fca416-5573-4662-a31a-a4ff808c34dd": "rejected",
    "af777ea8-1874-463c-a97c-a1f9e494bee1": "approved",
    "73031050-2016-44fc-b8f6-b97184793587": "approved"
  },
  "moderatorId": "b00916ba-f647-4e9f-b2a6-537f69f89b87",
  "moderatorEmail": "catherine@email.com",
  "moderatorExternalId": "foo-bar-baz"
}

2.2. Response

CleanSpeak expects that the event request handler (the Webhook you write) will return a 200 status code if the event was successfully handled or any other status code if it wasn’t. If your event request handler does not return a 200, CleanSpeak will place the content items back into the pre-approval queue so that they can be moderated again.

3. User Actions

The User Action system provides moderators the capability to perform specific actions on users that generate content. Actions such as ban, mute, and kick are a few examples of actions that moderators can take when, for instance, users are generating inappropriate content. When actions are taken, CleanSpeak will send events back into your system.

3.1. Request

Table 2. Request Body

type [String]

This parameter specifies the type of event and will always be userAction for this type of event.

applicationIds [Array<UUID>]

This parameter specifies the list of applications that the action is being performed in.

action [String]

This parameter specifies the name of the action that’s occurring.

comment [String]

The comment that the moderator added when taking the action. This value will be an empty string if a comment is not added.

expiry [Integer]

The instant that the action will expire, if the action expires.

key [String]

This parameter specifies the name of the key that’s occurring for the given action if the key exists. null will be returned if the action is time-based.

localizedAction [String]

This parameter specifies the localized name of the action that’s occurring, based on the user’s preferred languages.

localizedDuration [String]

The duration of the action in a human readable format that is localized based on the user’s preferred languages.

localizedKey [String]

This parameter specifies the localized key of the action that’s occurring, based on the user’s preferred languages.

localizedReason [String]

This parameter specifies the localized reason of the action that’s occurring, based on the user’s preferred languages.

moderatorEmail [String]

This parameter specifies the email of the moderator that performed the action that resulted in the event being sent.

moderatorExternalId [String]

This parameter specifies the external id (if one is configured) of the moderator that performed the action that resulted in the event being sent.

moderatorId [UUID]

This parameter specifies the id of the moderator that performed the action that resulted in the event being sent.

notifyUser [Boolean]

This parameter specifies whether the user should be notified or not.

phase [Boolean]

This parameter specifies the phase for the action. It is one of the following:

  • start

  • modify

  • cancel

  • end

When the action is started by a moderator, the phase will be "start". If a moderator changes the duration of the action, the phase will be "modify". If a moderator cancels an action it will be "cancel" or the action expires, the phase will be "end". If the action is key-based, the phase will be "start".

reason [String]

The reason the moderator selected.

Reasons are configured in the management interface via Settings → User Actions → Reasons. This value will be null when no reasons are selected or configured.

reasonCode [String]

The reason code the moderator selected.

Reasons are configured in the management interface via Settings → User Actions → Reasons. This value will be null when no reasons are selected or configured.

userId [UUID]

This parameter specifies the unique identifier of the user the action is being performed. This ID is equivalent to the ‘contentItem.senderId’ you provide when moderating and storing content.

email [Object]

Email object. See Example POST body below for fields.

Example Request JSON
{
  "type": "userAction",
  "applicationIds": [
    "2a6972a9-d332-458f-9c11-aa0eb74cfefc",
    "a1d7c8d2-be38-4530-8c61-b32245f94f0c",
    "def77957-1818-4fd5-b052-004777acb6fa",
    "96ebce2f-f9c0-44f6-a92f-6f476e08b678",
    "563215b9-b819-4ec5-b983-88174f26e390"
  ],
  "moderatorId": "1219c8e2-c0c2-4efc-9323-6ee9062e9c1f",
  "moderatorExternalId": null,
  "moderatorEmail": "moderator@cleanspeak.com",
  "action": "Mute",
  "comment": "a comment",
  "expiry": 1408554564119,
  "key": null,
  "localizedAction": "Mute",
  "localizedDuration": "2 days",
  "localizedKey": null,
  "localizedReason": "Misconduct",
  "notifyUser": true,
  "phase": "start",
  "reason": "Misconduct",
  "reasonCode": "123",
  "userId": "32ac49fe-1f7f-40b6-a3a1-02611a10945a",
  "email": {
    "attachments": [],
    "bcc": [],
    "cc": [],
    "from": {
      "address": "no-reply@yourorganization.com",
      "display": "no-reply@yourorganization.com"
    },
    "html": "no-reply@yourorganization.com",
    "replyTo": null,
    "subject": "You've received a Misconduct action",
    "text": ".... insert text here ....",
    "to": [
      {
        "address": "Allan249@example.com",
        "display": null
      }
    ]
  }
}

3.2. Response

CleanSpeak expects that the event request handler will return a 200 status code if the event was successfully handled or any other status code if it wasn’t. If your event request handler does not return a 200, CleanSpeak will assume that the action was not successful and display an error to the moderator.

4. Content Edits

Depending on your Application configuration, persistent content within CleanSpeak can be edited by moderators. When content is edited, CleanSpeak will notify your system so that your system can process the edit appropriately.

4.1. Request

Table 3. Request Body

type [String]

This parameter specifies the type of event and will always be contentEdit for this type of event.

applicationId [UUID]

The UUID of the application that owns the content item.

id [UUID]

The UUID of the content item that was edited.

moderatorEmail [String]

This parameter specifies the email of the moderator that performed the edit that resulted in the event being sent.

moderatorExternalId [String]

This parameter specifies the external id (if one is configured) of the moderator that performed the edit that resulted in the event being sent.

moderatorId [UUID]

This parameter specifies the id of the moderator that performed the edit that resulted in the event being sent.

newParts [Array<String>]

The new versions of each content part.

Example Request JSON
{
  "type": "contentEdit",
  "applicationId": "4f0a7e99-f533-4b89-bb4c-eb9ab36c4841",
  "id": "e8e93ee8-fedb-4238-8604-bd631967013c",
  "newParts": [
    "Multiple versions #3 changed"
  ],
  "moderatorId": "b00916ba-f647-4e9f-b2a6-537f69f89b87",
  "moderatorEmail": "catherine@email.com",
  "moderatorExternalId": "foo-bar-baz"
}

4.2. Response

CleanSpeak expects that the event request handler will return a 200 status code if the event was successfully handled or any other status code if it wasn’t. If your event request handler does not return a 200, CleanSpeak will assume that the edit was not successful and display an error to the moderator.

5. Content Deletes

Depending on your Application configuration, persistent content within CleanSpeak can be deleted by moderators. When content is deleted, CleanSpeak will notify your system so that your system can process the delete appropriately.

5.1. Request

Table 4. Request Body

type [String]

This parameter specifies the type of event and will always be contentDelete for this type of event.

applicationId [UUID]

The UUID of the application that owns the content item.

id [UUID]

The UUID of the content item that was deleted.

moderatorEmail [String]

This parameter specifies the email of the moderator that performed the delete that resulted in the event being sent.

moderatorExternalId [String]

This parameter specifies the external id (if one is configured) of the moderator that performed the delete that resulted in the event being sent.

moderatorId [UUID]

This parameter specifies the id of the moderator that performed the delete that resulted in the event being sent.

newParts [Array<String>]

The new versions of each content part.

Example Request JSON
{
  "type": "contentDelete",
  "applicationId": "63d797d4-0603-48f7-8fef-5008edc670dd",
  "id": "3f8f66cb-d933-4e5e-a76d-5b3a4d9209cd",
  "moderatorId": "b00916ba-f647-4e9f-b2a6-537f69f89b87",
  "moderatorEmail": "catherine@email.com",
  "moderatorExternalId": "foo-bar-baz"
}

5.2. Response

CleanSpeak expects that the event request handler will return a 200 status code if the event was successfully handled or any other status code if it wasn’t. If your event request handler does not return a 200, CleanSpeak will assume that the delete was not successful and display an error to the moderator.

6. Filter Approvals

Available since 3.27.0

When filter configuration is changed via the UI, the configuration goes to a pending approval state. Only when that configuration is approved by an administrator will it become active in the live state of the filters used by the webservice. When this approval happens, you can view the changes via this event. (Note: Changes via the API bypass the approval queue and thus will not trigger this event)

6.1. Request

Table 5. Request Body

type [String]

This parameter specifies the type of event and will always be fitlerApproval for this type of event.

blacklist.blacklistWhitelistEntries.current [Blacklist Whitelist Entry]

The current state of the Blacklist Whitelist Entry after approval. This is the state of the object in the filter.

blacklist.blacklistWhitelistEntries.original [Blacklist Whitelist Entry]

The original state of the Blacklist Whitelist Entry before the approval.

blacklist.filterEntries.current [Blacklist Filter Entry]

The current state of the Blacklist Filter Entry after approval. This is the state of the object in the filter.

blacklist.filterEntries.original [Blacklist Filter Entry]

The original state of the Blacklist Filter Entry before the approval.

blacklist.dictionaryEntries.current [Dictionary Entry]

The current state of the Dictionary Entry after approval. This is the state of the object in the filter.

blacklist.dictionaryEntries.original [Dictionary Entry]

The original state of the Dictionary Entry before the approval.

blacklist.phrases.current [Blacklist Phrase]

The current state of the Blacklist Phrase after approval. This is the state of the object in the filter.

blacklist.phrases.original [Blacklist Phrase]

The original state of the Blacklist Phrase before the approval.

whitelist.advancedDisallowedEntries.current [Whitelist Advanced Disallowed Phrase]

The current state of the Whitelist Advanced Disallowed Entry after approval. This is the state of the object in the filter.

whitelist.advancedDisallowedEntries.original [Whitelist Advanced Disallowed Phrase]

The original state of the Whitelist Advanced Disallowed Entry before the approval.

whitelist.allowedEntries.current [Whitelist Allowed Word]

The current state of the Whitelist Allowed Entry after approval. This is the state of the object in the filter.

whitelist.allowedEntries.original [Whitelist Allowed Word]

The original state of the Whitelist Allowed Entry before the approval.

whitelist.disallowedEntries.current [Whitelist Disallowed Phrase]

The current state of the Whitelist Disallowed Entry after approval. This is the state of the object in the filter.

whitelist.disallowedEntries.original [Whitelist Disallowed Phrase]

The original state of the Whitelist Disallowed Entry before the approval.

url.tlds.current [TLD]

The current state of the TLD after approval. This is the state of the object in the filter.

url.tlds.original [TLD]

The original state of the TLD before the approval.

url.whitelistedURLs.current [Whitelisted URL]

The current state of the Whitelisted URL after approval. This is the state of the object in the filter.

url.whitelistedURLs.original [Whitelisted URL]

The original state of the Whitelisted URL before the approval.

Example Request JSON
{
  "type": "filterApproval",
  "changes": {
    "blacklist": {
      "blacklistWhitelistEntries": [
        {
          "current": {
            "id": 94,
            "pattern": "magna cum laude",
            "status": "ACTIVE"
          },
          "original": {
            "id": 94,
            "pattern": "magna cum laude",
            "status": "ADDED"
          }
        },
        {
          "current": {
            "id": 95,
            "pattern": "[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}",
            "status": "ACTIVE"
          },
          "original": {
            "id": 95,
            "pattern": "([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}){1}",
            "status": "EDITED"
          }
        },
        {
          "original": {
            "id": 96,
            "pattern": "bad.+match",
            "status": "DELETED"
          }
        }
      ],
      "dictionaryEntries": [
        {
          "current": {
            "id": 117,
            "locale": "en",
            "status": "ACTIVE",
            "tags": [],
            "text": "mom"
          },
          "original": {
            "id": 117,
            "locale": "en",
            "status": "ADDED",
            "tags": [],
            "text": "mom"
          }
        },
        {
          "original": {
            "id": 118,
            "locale": "en",
            "status": "DELETED",
            "tags": [],
            "text": "batman"
          }
        },
        {
          "current": {
            "id": 119,
            "locale": "en_US",
            "status": "ACTIVE",
            "tags": [],
            "text": "donald"
          },
          "original": {
            "id": 119,
            "locale": "en",
            "status": "EDITED",
            "tags": [],
            "text": "dolan"
          }
        }
      ]
    },
    "url": {},
    "whitelist": {}
  }
}

6.2. Response

CleanSpeak expects that the event request handler will return a 200 status code if the event was successfully handled or any other status code if it wasn’t. If your event request handler does not return a 200, CleanSpeak will assume that the approval was not successful and display an error.

7. Event Handler

In order to appropriately handle requests from the CleanSpeak event system, you must build a simple HTTP request handler that listens for requests from the CleanSpeak Moderation event system. Your request handler must be designed to respond to simple HTTP POST requests with a request body containing application/json data as described above.

7.1. Responses

Your event handler must handle the REST API request described above and send back an appropriate response. Your event webService must send back to CleanSpeak an HTTP response code that indicates whether or not the event was successfully handled or not. If your WebService handled the event properly, it must send back an HTTP response status code of 200. If there was any type of error or failure, your WebService must send back an HTTP response status code of 500.

7.2. Configuration

Once your event handler is complete and listening for event requests, you must configure your event handler URL. CleanSpeak refers to event handlers as event Servers and they are configured via the Management Interface under Settings → Webhooks menu option. If you have multiple event servers configured for a single Application, they must all correctly handle the event for the transaction to succeed.

7.3. Securing

CleanSpeak sends JSON events to your configured Webhooks that might include user information or other sensitive data. Therefore, it is important to ensure that your Webhooks are secured properly to prevent data from being leaked. This section covers the standard methods for securing Webhooks.

7.3.1. TLS v1.2

The first step in securing your Webhooks is to ensure that they are using TLS v1.2. You should be using a web server that is configured with a certificate from a valid certificate authority and be configured to only receive traffic that is over a secure connection. We also recommend that you disable all older secure protocols including SSL, TLS 1.0 and TLS 1.1.

If you need a certificate, most cloud providers offer certificates or you can use LetsEncrypt to generate a certificate and ensure it is always up-to-date.

7.3.2. Headers

When you configure your Webhook with CleanSpeak, you should include a security header of some kind. There are two ways to define a security header:

  • Add a Basic Authentication username and password under the Security tab

  • Define an HTTP header under the Headers tab

In either case, your Webhook code should validate the security header to ensure the event is coming from CleanSpeak. Here’s some example code that validates an Authorization header:

Example Webhook
router.route('/cleanspeak-webhook').post((req, res) => {
  const authorization = req.header('Authorization');
  if (authorization !== 'API-KEY') {
    res.status(401).send({
      'errors': [{
        'code': '[notAuthorized]'
      }]
    });
  } else {
    // ...
  }
});

7.3.3. Firewalls

In addition to using TLS and a security header, you might also want to put a firewall in front of your Webhook. In most cases, this firewall will only allow traffic to your Webhook that originated from your CleanSpeak server. Depending on how you are hosting your Webhook, you might be able to lock down the URL for your Webhook specifically. You might also leverage an API gateway or a proxy to ensure that only traffic coming from CleanSpeak is routed to your Webhook. The exact specifics of deploying and configuring a Firewall are outside the scope of this document, but you can consult the documentation for your proxy, API Gateway or hosting provider to determine how to manage the firewall.

As an example, you can configure an AWS Application Load Balancer so that traffic coming from the IP address of your CleanSpeak servers with a URL of https://apis.mycompany.com/cleanspeak-webhook is routed through. You can then configure the Application Load Balancer so that all other traffic to that URL is rejected.