Unleash the power of BuiltOn’s webhooks!

Nicolas Bonduel, Frontend Developer @ BuiltOn.dev on .

Share with Facebook
Share with Twitter
Share with LinkedIn

APIs and webhooks are two technologies that keep track and relay data between two services. Both of them are extremely useful, and have slightly different purposes.

Webooks

For any less experienced devs out there, APIs are useful for receiving, creating, updating or deleting information on demand, whereas webhooks allow you to get notifications whenever certain events occur.

You can use webhooks within your BuiltOn application and always be aware of data driven change, such as when a user is created, a payment is successful or an order status is changed (see our list of webhook events here).

As an introduction to the use of webhooks, this article takes you through one use case: how to send an alert message to Slack anytime a new user registers for your company.

Prerequisites:

Don’t worry, they’re all free.

Here is how it will work:

Webhooks 2

Step 1: Create a basic Webhook function

Create a folder and instantiate the project with npm:

mkdir builton-slack-alert
cd builton-slack-alert
npm init -y

Install serverless and the serverless-offline plugin for debugging:

npm install serverless -g
npm install serverless-offline --only-dev

Serverless will allow you to deploy the webhook handler on AWS. With a few small changes, it will also allow you to deploy it to other services like Google Cloud or Microsoft Azure.

Create a serverless.yml file at the root of your folder with the following content:

service: user-created-slack-alert

provider:
  name: aws
  runtime: nodejs10.x
  stage: ${opt:stage, 'dev'}

plugins:
  - serverless-offline

functions:
  user_created: # A Function
    handler: index.user_created # The file and module for this specific function.
    events:
      - http:
          path: user_created
          method: post

This file describes how the deployment of the handler should occur. You specify NodeJS 10 as the runtime and AWS as the provider. The function will be called user_created and will be accessible in the index (index.user_created). It will trigger on POST http events under the path / user_creted.

As a quick test, you can write our basic handler in the index.js file.

module.exports.user_created = async (event) => {
  console.log(event);
  return {
    statusCode: 200
  };
};

You can now quickly test if the handler receives any data when called. In one terminal tab, run the following to deploy the function locally:

serverless offline

In a second tab, use curl to call this function and check if everything works:

curl -XPOST -d '{"Hello": "World"}' 'http://localhost:3000/user_created'

Note: This CURL command will not output anything.

The first tab (with serverless running) should now display the content of the request. This means everything is working as intended.

Step 2: Connect your function with BuiltOn

Now, I would recommend localtunnel to expose your handler to the world and allow our API to start communicating with it. You can install and run it with the following commands:

npm install localtunnel -g
lt --port 3000

Localtunnel should give you a URL similar to https://plastic-stingray-32.localtunnel.me. This URL allows the external world to access your function.

Use curl to check if everything still works. Make sure your serverless function is still running and run the command:

curl -XPOST 'https://plastic-stingray-32.localtunnel.me/user_created'

If everything runs correctly, we can go ahead and set up this URL in BuiltOn. We would recommend creating a QA company for that purpose.

On your BuiltOn dashboard, under the webhooks section, create a new webhook. The endpoint should be your URL, e.g. https://plastic-stingray-32.localtunnel.me/user_created, and the event type should be user.created.

Create new Webhook

This should trigger a ping request, which will detect if your endpoint is accessible.

Step 3: Handle webhook data

Two types of requests can be calling your function from BuiltOn:

  • WebhookConfirmation, which detects if your endpoint is accessible (like a ping).
  • WebhookMessage, which will receive data when the event is triggered.

To detect which kind of request you receive, you can create a utility function. Create a file named utils.js with the following functions:

// Headers are case insensitive, they may be automatically modified depending on the server you're using.
const getHeader = (headers, key) => {
  const trueKey = Object.keys(headers).find(k => k.toLowerCase() === key.toLowerCase());
  return headers[trueKey];
}

module.exports.isPing = (event) => {
  const webhookType = getHeader(event.headers, 'x-builton-webhook-type');
  if (webhookType && webhookType === 'WebhookConfirmation') {
    console.log('pong');
    return true;
  }
  return false;
}

This isPing function takes the request event and will return a boolean depending on its type.

We can now use it in the main file:

const utils = require('./utils');

module.exports.user_created = async (event) => {
  if(utils.isPing(event)) {
    return {
      statusCode: 200
    };
  }
};

Let’s print the email of any newly created user, as a proof of concept.

const utils = require('./utils');

module.exports.user_created = async (event) => {
  if(utils.isPing(event)) {
    return {
      statusCode: 200
    };
  }
  const body = JSON.parse(event.body);
  const user = body.object;
  console.log(user.email);
};

You can now simulate a webhook event by going into the dashboard and clicking the simulate(the little fan icon) button in the webhook’s details you previously created.

Webhook fan

The serverless output should now look something like this!

Serverless: POST /user_created (λ: user_created)
john.doe@example.com

To ensure that no one impersonates the BuiltOn webhook, you can add a security check. This is where the secret token comes into play. To verify that the incoming requests are legitimate, you should hash the response body with this SHA-256 secret and verify that it matches the X-BuiltOn-Webhook-Signature header you receive in the request.

For this, you want to create an environment variable that will hold the secret. You can edit the serverless.yml file as such:

service: user-created-slack-alert

provider:
  name: aws
  runtime: nodejs10.x
  stage: ${opt:stage, 'dev'}

plugins:
  - serverless-offline

functions:
  user_created: # A Function
    handler: index.user_created # The file and module for this specific function.
    environment:
      webhookSecret: 'YOUR_WEBHOOK_SECRET' # Replace this with your webhook secret.
    events:
      - http:
          path: user_created
          method: post

After restarting serverless, the variable will be accessible with process.env.webhookSecret.

You can now create a second utility function verifySecret to verify the signature in the utils.js file:

const crypto = require('crypto');

// Headers are case insensitive, they may be automatically modified depending on the server you're using.
const getHeader = (headers, key) => {
  const trueKey = Object.keys(headers).find(k => k.toLowerCase() === key.toLowerCase());
  return headers[trueKey];
}

module.exports.isPing = (event) => {
  const webhookType = getHeader(event.headers, 'x-builton-webhook-type');
  if (webhookType && webhookType === 'WebhookConfirmation') {
    console.log('pong');
    return true;
  }
  return false;
}

module.exports.verifySecret = (event) => {
  const signature = getHeader(event.headers, 'x-builton-webhook-signature');
  const generatedSignature = crypto.createHmac('sha256', process.env.webhookSecret).update(event.body).digest('hex');
  if (generatedSignature === signature) {
    return true;
  }
  return false;
}

You can now use it in the index.js file:

const utils = require('./utils');

module.exports.user_created = async (event) => {
  if(!utils.verifySecret(event)) {
    return {
      statusCode: 401
    };
  }
  if(utils.isPing(event)) {
    return {
      statusCode: 200
    };
  }
  const body = JSON.parse(event.body);
  const user = body.object;
  console.log(user.email);
};

Step 4: Send the alert to Slack

Create a Slack app by clicking this link. Choose an app name and a team as you wish.

Once done, you should see something like:

Slack dashboard

Click on Incoming Webhooks, activate it and click on Add New Webhook to Workspace at the bottom.

You will be prompted with something like the following screen:

Slack dashboard 2

Choose which channel or person you want the new users to be posted to, and then Authorize your app. You should be sent back to the previous screen with a new webhook URL you can copy.

Slack dashboard 3

Copy this URL, and as we did with the secret token, we can set it up as an environment variable for the function:

service: user-created-slack-alert

provider:
  name: aws
  runtime: nodejs10.x
  stage: ${opt:stage, 'dev'}

plugins:
  - serverless-offline

functions:
  user_created: # A Function
    handler: index.user_created # The file and module for this specific function.
    environment:
      webhookSecret: 'YOUR_WEBHOOK_SECRET' # Replace this with your webhook secret
      slackUrl: 'YOUR_SLACK_WEBHOOK_URL' # Replace this with your slack webhook url
    events:
      - http:
          path: user_created
          method: post

From here, we only need to send a simple request to Slack. I would suggest using a library like node-fetch to do so.

Install it using npm:

npm install node-fetch

Once this is done, you can send a request with the name and email of the newly created user to Slack when the function is triggered:

const fetch = require('node-fetch');
const utils = require('./utils');

module.exports.user_created = async (event) => {
  if(!utils.verifySecret(event)) {
    return {
      statusCode: 401
    };
  }
  if(utils.isPing(event)) {
    return {
      statusCode: 200
    };
  }
  const body = JSON.parse(event.body);
  const user = body.object;
  try {
    await fetch(process.env.slackUrl, {
      method: 'POST',
      body: JSON.stringify({
        text: `New signup! ${user.first_name} ${user.last_name} (${user.email})`
      })
    });
    return {
      statusCode: 200
    };
  } catch (err) {
    return {
      statusCode: 500,
      err
    }
  }
};

That’s it! Go ahead and try to simulate the webhook from your BuiltOn dashboard, and a message should be sent to your chosen Slack channel!

Step 5: Upload your function to AWS Lambda

AWS Lambda is free as long as you don’t exceed the resources in the free tier, which will, in most cases, be large enough for your needs.

I would recommend following this article on how to set up your AWS account with Serverless.

Once this is done, you should be able to deploy your function on AWS with:

serverless deploy

You should get an output similar to this:

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service user-created-slack-alert.zip file to S3 (78 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
................................
Serverless: Stack update finished...
Service Information
service: user-created-slack-alert
stage: dev
region: us-east-1
stack: user-created-slack-alert-dev
resources: 10
api keys:
  None
endpoints:
  POST - https://34et4ty4rv.execute-api.us-east-1.amazonaws.com/dev/user_created
functions:
  user_created: user-created-slack-alert-dev-user_created
layers:
  None

You now have a new endpoint, hosted on AWS’ servers, something like:

https://34et4ty4rv.execute-api.us-east-1.amazonaws.com/dev/user_created

Replace the localtunnel one with the AWS one on your BuiltOn dashboard and everything should work as expected.

BuiltOn Dashboard 2

Click on simulate to trigger an event instantly with test data:

Webhooks - New event

Nailed it!

Nailed it

You now know the gist of using BuiltOn’s webhooks to power custom workflows and business logic. Webhooks bring greater efficiency and endless possibilities. The real power and value lie in what you choose to do with them. Your logic is executed based on real-time data from BuiltOn. Here are some examples to give you ideas:

  • Book a delivery or pick-up service to show up at your customer's door.

  • Trigger a piece of hardware to fire when an order is completed, e.g. lock a car door or activate an electric scooter.

  • Notify your customers when a new product is introduced.

  • Create an invoice and print it automatically.

For more information on webhooks in the BuiltOn platform, visit our docs: https://docs.builton.dev/building-blocks/webhooks or contact us at hello@builton.dev.

loading...