Step by Step Webhook Tutorial

The best way to understand how to use webhooks is to practice doing one.

This tutorial is for client developers and is a step by step guide in how to create and use a webhook in Bracken.
In this tutorial you will create a public endpoint using Ngrok, and go through the authorisation process to get an access token to be able to subscribe to a webhook, then observe the response and authenticate the payload.

This tutorial was created using the editor Visual Studio Code, Ngrok software, Python 3.6.8 and Node.js

Prerequisites

  • Familiarity with the command line (only for the purpose of this tutorial)
  • NPM is installed (only for the purpose of this tutorial)
  • Already have a Bracken domainkey, client_id and client_secret

Create a new project

Create a new project folder called ‘Bracken-webhooks-test’ and move into that directory. This folder will contain your Ngrok file and Node.js project. The end product of this will be a public endpoint which the webhook payload will be sent to when the event is triggered.

Note We will be triggering the webhook event to understand what they payload will look like.

In the example code this project folder is called “Bracken-Webhooks-Test”

Create an Express app

  1. Open your preferred editor and move into the directory that we have just created.
  2. Open a new terminal and run npm init.
  3. Accept the default values that are created in the package.json file.

Your express app should look like this with package.json, package-lock.json, > node_modules inside.

  1. install Express by running npm install express in your terminal.

  2. Create a new file called index.js In this file we are going to create an instance of express and create endpoints to respond to the incoming requests.

  3. Within index.js add the following code. The comments explain what each part does.

// Enable express to be used
const express = require('express');
// app is an instance of express.
const app = express(); 

// app.METHOD(PATH, HANDLER)
app.post('/webhooks', function (req, res) {
    
    // This will return a 200 with the message 'OK'
    res.send('OK')
});

// Respond to incoming get requests with ‘hello’:
app.get('/', (req, res) => {
    res.send('Hello!');
});

// The app is listening to requests on port 5000
app.listen(5000, function () {
    console.log('Listening for webhooks on port 5000')
})

Getting a public endpoint

For this tutorial we are using ngrok for a public endpoint.

If you already have a public endpoint available then you can skip the ngrok section of this tutorial and host the index.js on your own public endpoint and then when creating the webhook, use your public url as the target.

Install Ngrok

Ngrok is a software that creates a public url and therefore a public endpoint which is where our webhook payload will be sent to.

Note You can choose to create a Ngrok account although it is not needed for this tutorial.

  1. Go to https://ngrok.com/
  2. Click ‘Getting started for free’
  3. Download the correct Ngrok version for your operating system.

Unzip to install

On Linux or Mac OS X you can unzip ngrok from a terminal with the following command.
unzip /path/to/ngrok.zip

On Windows, just double click ngrok.zip to extract it.

  1. After unzipping the file, either drag the Ngrok.exe file into your project folder or use the terminal to move the Ngrok.exe file into your project folder.

Current Project:
The current project should now be one folder containing package.json, package-lock.json, > node_modules, index.js and ngrok.exe

Run project

  1. Save all changes you have made.
  2. Open a terminal window.
  3. Run index.js with the command node index.js in the terminal window
  4. Open a new terminal window.
  5. Start up Ngrok with the command ./ngrok http 5000 This will connect Ngrok with your localhost:5000 and the URL that Ngrok supplies will be the same as what appears on your localhost:5000

Trouble Shooting
If your Ngrok URL and localhost:5000 do not match then stop running Ngrok and rerun it using this command instead ./ngrok http 127.0.0.1:5000

Current Project:
You now have both your index.js file and Ngrok currently running

Create a Webhook

Now we are going to create a webhook and this means we are going to subscribe to one.
In this example we are going to sign up for the User_Create webhook.

Create a new project folder

In the example code this project is called “Control-Trigger” that is because this project folder will be used to create a webhook and trigger it.

Move to the “Control-Trigger” folder in your directory via your preferred editor.

Webhook User_Create

Create a new file called webhook-User_Create.py

In this file we are going to get an access token and sign up for the webhook that will get triggered when a new user is created.

Copy the Ngrok HTTPs URL. This is a public endpoint that can be accessed anywhere. This is going to be your target that you will create the webhook with.

Warning
Every time you run Ngrok you will have a different URL. Do not close Ngrok for this next part otherwise the target you give the webhook to send responses to will never see a response.

Create python file to create webhook

In the file webhook-User_Create.py add the following code but read the next section before running the file.

# Creating a webhook, triggers when new users are added.
import requests
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient

# declare your own values here. 
# domainkey is an integer
domainkey = YOUR DOMAIN KEY
client_id = 'YOUR CLIENT_ID'
client_secret = 'YOUR CLIENT_SECRET'
# Identity Server URL
token_url = 'https://identity.brackenlearning.com/connect/token'

# use your information to get authorization to access the API
client = BackendApplicationClient(client_id=client_id)
oauth = OAuth2Session(client=client)
token = oauth.fetch_token(token_url=token_url, client_id=client_id,client_secret=client_secret)

#request an access token
s = requests.Session()
s.headers.update({'Authorization': 'Bearer {}'.format(token['access_token'])})

# declare the webhook you wish to sign up to
urlMerge_Webhook = 'https://api.brackenlearning.com/api/webhook/merge/domain/{0}/event/user_create'.format(domainkey)

# create the webhook and call it. 
response = s.put(urlMerge_Webhook, json={"target":"https://224438f19e12.ngrok.io/webhooks", "secret":"12345", "active": True})

print(response)
print(response.json())

Note:
It is strongly advised to have a secure secret when creating any webhook.
Please change the secret if you are going to use this code.

  1. Add your own domainkey, client_id and client_secret values.

  2. Change the target to your Ngrok generates URL. Do not forget to add /webhooks to the end the URL.

  3. Open a new tab in your preferred web browser

  4. Copy this address into the URL address bar http://127.0.0.1:4040. This is a Web Interface generated by Ngrok and it is running on your local machine. This will enable us to see the responses generated.

  5. Run the python file webhook-User_Create.py

You should see a [200] response and the JSON payload that you have sent to Bracken.

Trigger Webhook

Now we want to trigger the webhook we have just created by triggering the event User_create. We can do this by creating a new user.

Create New User via API

We are going to create a new user via the API.

This will give us practice with interacting with the API and the project file name is called “Control-Trigger”. We have done the control part by creating a webhook and controlling where the payload will be sent and now we need to trigger the webhook to confirm we have done it right.

Create python file to trigger webhook

  1. Create a new python file and call it newUser.py
  2. Add the following code to the file
# Creating a webhook, triggers when new users are added.
import requests
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient

# declare your own values here. 
# domainkey is an integer
domainkey = YOUR DOMAIN KEY
client_id = 'YOUR CLIENT_ID'
client_secret = 'YOUR CLIENT_SECRET'
# Identity Server URL
token_url = 'https://identity.brackenlearning.com/connect/token'

# use your information to get authorization to access the API
client = BackendApplicationClient(client_id=client_id)
oauth = OAuth2Session(client=client)
token = oauth.fetch_token(token_url=token_url, client_id=client_id,client_secret=client_secret)

#request an access token
s = requests.Session()
s.headers.update({'Authorization': 'Bearer {}'.format(token['access_token'])})

# URL to create a new user
urlCreateUser = 'https://api.brackenlearning.com/api/user/create/domain/{0}'.format(domainkey)

# Send a POST to create a new user in your domain
response = s.post(urlCreateUser, json={"username":"Test-User", "password":"JSue5be8wuI8kei328Jdu0ew*3js"})

print(response)
print(response.json())

You can change the JSON body to your preferred username and password.

WARNING
It is strongly advised when creating a new user to create a strong password.

Change the values to interact with your own domain. Run the python file. The output will be a new test user.

Confirm Webhook was Triggered

We have now created a user and in doing this we should have triggered the User_Create event, which triggers the webhook we have just created.

Open the Ngrok Web Interface tab in your web browser that we opened earlier on. You should see a POST/webhooks 200 OK in the requests panel.

If you click on this you will be able to see the the payload of the Webhook and we can confirm that a new user was created called “Test-User” ha been created.

Authenticate Webhook

Now that we have the received the payload we need to confirm that it came from Bracken. The Ngrok URL is a public endpoint which means that anyone on the internet can send information to the endpoint. We can use the header of the payload to confirm that it came from Bracken by encrypting the secret that we created with the body of the payload. By encrypting this we can then match it to the already encrypted header of the payload and if the two match it means that the payload came from Bracken.

Note: This is why we suggest to use a secure secret when signing up for our webhooks because it is used to authenticate the response.

  1. Go to the “Bracken-Webhook-Test” folder and open index.js file. We are going to modify this file so that when the webhook response is received, it is instantly authenticated and we will only receive a [200] response if the webhook came from Bracken.
  2. Add the following code to index.js. The un-commented code is what you should add.
// const express = require('express');
// const app = express(); //app is an instance of express

// library to assist with encryption
const crypto = require('crypto');
// same secret you declared when creating the webhook 
const secret = "12345";
// the front of the header will always have this.
const front = "HMACSHA256 ";


// when there's a post request to /webhooks
//app.METHOD(PATH, HANDLER)
app.post('/webhooks', function (req, res) {

    const hash = crypto
        .createHmac('sha256', secret)
        .update(JSON.stringify(req.body))
        .digest('base64');

    const yourSignature = front + hash;

    const brackenWebhookSignature = req.header('Authorization');

    if ((brackenWebhookSignature != null) && brackenWebhookSignature == yourSignature) {
        res.send('OK the signatures match')
    } else {
        return res.status(403).send('Do not trust, not from a Bracken webhook');
    }
});

// respond to incoming get requests with ‘hello’:
// app.get('/', (req, res) => {
//     res.send('Hello! wtf are you connected to!?');
// });

// app.listen(5000, function () {
//     console.log('Listening for webhooks on port 5000')
// })

Authentication code explanation

By adding this code to index.js we will now receive ‘OK the signatures match’ if the webhook comes from Bracken and ‘Do not trust, not from a Bracken webhook’ if the response is not from Bracken.
In the code above we have encrypted the secret and the JSON body using the HMACSHA256 algorithm which produces a binary digest, then we use base64 to encode it as a string output.

To do this we used crypto which is a Node.js module that provides cryptographic functionality.
We declare it at the start of the file

const crypto = require('crypto'); 

and then use it to hash the secret and JSON body within the method that catches the incoming webhook responses.

We do this by creating a constant called hash using the crypto module.
First we create an Hash-Based Message Authentication Code (HMAC) specifying the HMACSHA256 algorithm to encrypt the secret.

const hash = crypto
    .createHmac('sha256', secret)

Then we update the hash to include the JSON body of the response.

    .update(JSON.stringify(req.body))

Lastly we encode the hash using base64 encoding, because need to encode the binary data that the HMACSHA256 algorithm produces and convert it to textual data, so that it is comparable to our response header.

    .digest('base64');

Now we have encrypted our secret and the JSON body from the response that we received. Next in the code we are comparing if our signature matches the Authorization header from the response we received.

    const yourSignature = front + hash;

    const brackenwebhooksignature = req.header('Authorization');

    if ((brackenwebhooksignature != null) && brackenwebhooksignature == yourSignature) {
        res.send('OK the signatures match')
    } else {
        return res.status(403).send('Do not trust, not from a Bracken webhook');
    }

If the signatures match then you can trust the response and then react to the data how you see fit.

Clean up

In the source code provided there is a python file called deleteUser.py you can use this to delete the ‘Test-User’ you have created during this tutorial. This code is identical to the newUser.py file except the url has changed so it will delete a user instead.

When you wish to create a webhook on your own system you can simply run the webhook again but this time change the target to your public endpoint

Thank you for reading this step by step tutorial on Bracken Webhooks.