Examples

Create your webhook endpoint

The first thing to do is to create an API with an endpoint accepting POST requests.

const express = require('express')
const crypto = require('crypto')

// Constants declarations
const PORT = 8080
const KEY = 'YOUR_OWN_SECRET_KEY' // The same key to set-up later in the dashboard
const ALGO = 'sha256'

const app = express()

// Enable Express to use JSON data format
app.use(express.json())

// Declare your endpoint: POST http://localhost:8080/webhook
app.post('/webhook', (req, res) => {
	// Get the "challenge" value from the request's body
	const { challenge } = req.body

	// Get the signature of the request from the request's headers
	const signature = req.headers['x-hub-signature']

	// Now let's generate the HMAC SHA256 Hex digest of the
	// request body using the secret key to verify the authenticity of the request.
  // Note that the signature header has been generated with a stringified JSON without any whitespaces.
  // Therefore, to generate your own expected signature, your body MUST be stringified without any separator nor whitespace.
	const hash = crypto.createHmac(ALGO, KEY).update(JSON.stringify(req.body)).digest('hex')

	// Now check the integrity
	if (hash !== signature) {
		// Do something with the integrity failure
	}

	// Now get the full notification data
	const notification = req.body

	// Do stuff with the received notification payload

	// Now let's build the response to send to Bodyguard
	const response = { challenge }

	// And send it back
	res.header('Content-Type', 'application/json').send(response)
})

// Run your tiny API
app.listen(PORT, () => console.log(`Webhook example API running on port ${PORT}`))

Then, simply run your API:

 node index.js
Webhook example API running on port 8080

Expose your endpoint

Now that we have our tiny API running and waiting for incoming notifications, it HAS to be accessible from the Internet and through HTTPS protocol.

To expose your endpoint as required, you can use ngrok or localtunnel.

Once installed and set up correctly, run this command:

cURL

 ngrok http 8080
ngrok by @inconshreveable                                                                                                                                                                                                     (Ctrl+C to quit)

Session Status                online
Account                       <your_ngrok_registered@email.address> (Plan: Free)
Version                       2.3.40
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://ede9-109-2-22-27.ngrok.io -> http://localhost:8080
Forwarding                    https://ede9-109-2-22-27.ngrok.io -> http://localhost:8080

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

As we can see, ngrok has deployed two temporary internet-accessible endpoints that tunnel toward your locally running API:

  • http://ede9-109-2-22-27.ngrok.io
  • https://ede9-109-2-22-27.ngrok.io

As mentioned earlier, we will use ONLY HTTPS connections to send notifications. Therefore, we will use the second option for the following steps.

Test your endpoint

Before you can register and use your webhook, a testing process must be performed to ensure that everything is well-configured and ready to use. After you have entered the information about your webhook on the dashboard, click the "Test Integration" button to manually launch the testing process.

It will send an HTTP POST request to the webhook URL you provided, with this simple payload:

Payload

{
	"challenge": "7cbe2b6a0a5e58934bc3c5ea77be7de6de77e633a1432f0e1883584d135045cd"
}

To validate the testing process, simply return the challenge as requested in the challenge section. If you don't send the expected response, the testing process will fail and you won't be able to register and use your Webhook.


Take a look at some use cases:

MessageUpdated payload

Payload

{
	"version": 1,
	"organization": {
		"id": "5d849494-2b0e-49fc-85d5-7197ca62d7fe",
		"name": "Bodyguard"
	},
	"time": 1630940073706,
	"eventType": "MESSAGE_UPDATED",
	"challenge": "54cddd8ead45bb5661ce229ca67b674e576b959f596851428c4f711724b9cb95",
	"messageUpdatedData": {
		"previousMessage": {
			"resourceType": "TEXT",
			"type": "HATEFUL",
			"classifications": ["BODY_SHAMING", "INSULT"],
			"publishedAt": "2021-06-11T03:42:00.420Z",
			"directedAt": "SINGLE_PERSON",
			"recommendedAction": "KEEP",
			"severity": "HIGH",
			"text": "lol",
			"reference": "bodyguard/6d0f43f2-5058-4cf1-869f-14a6a52a8b4b"
		},
		"newMessage": {
			"resourceType": "TEXT",
			"type": "HATEFUL",
			"classifications": ["BODY_SHAMING", "INSULT"],
			"publishedAt": "2021-06-11T03:42:00.420Z",
			"directedAt": "SINGLE_PERSON",
			"recommendedAction": "REMOVE",
			"severity": "HIGH",
			"text": "lol",
			"reference": "bodyguard/6d0f43f2-5058-4cf1-869f-14a6a52a8b4b"
		},
		"channelId": "cbc601f1-dec6-4594-9476-51628a17499a",
		"sourceId": "81310ae5-228e-438e-9720-35f737b9ca9f"
	}
}

ContentUpdated payload

Payload

{
	"version": 1,
	"organization": {
		"id": "5d849494-2b0e-49fc-85d5-7197ca62d7fe",
		"name": "Bodyguard"
	},
	"time": 1630940073706,
	"eventType": "CONTENT_UPDATED",
	"challenge": "54cddd8ead45bb5661ce229ca67b674e576b959f596851428c4f711724b9cb95",
	"contentUpdatedData": {
		"previousContent": {
			"permalink": "https://link.to.post",
			"title": "Hey check this out",
			"type": "VIDEO",
			"status": "PUBLISHED",
			"identifier": "post-1234-abcd"
		},
		"newContent": {
			"permalink": "https://link.to.post",
			"title": "Hey check this out",
			"type": "VIDEO",
			"status": "REMOVED",
			"identifier": "post-1234-abcd"
		},
		"sourceId": "81310ae5-228e-438e-9720-35f737b9ca9f"
	}
}

AuthorUpdated payload

Payload

{
	"version": 1,
	"organization": {
		"id": "5d849494-2b0e-49fc-85d5-7197ca62d7fe",
		"name": "Bodyguard"
	},
	"time": 1630940073706,
	"eventType": "AUTHOR_UPDATED",
	"challenge": "54cddd8ead45bb5661ce229ca67b674e576b959f596851428c4f711724b9cb95",
	"authorUpdatedData": {
		"previousAuthor": {
		  "classifications": [],
		  "flaggedAt": null,
			"permalink": "https://link.to.profile",
			"status": "ACTIVE",
			"identifier": "user/1a2b3c4d"
		},
		"newAuthor": {
		  "classifications": [],
		  "flaggedAt": null,
			"permalink": "https://link.to.profile",
			"status": "BANNED",
			"identifier": "user/1a2b3c4d"
		},
		"sourceId": "81310ae5-228e-438e-9720-35f737b9ca9f"
	}
}