DEV Community

loading...
Cover image for Writing an Azure Function in node.js  to implement a webhook
Zegami

Writing an Azure Function in node.js to implement a webhook

ahaith profile image Andy Haith ・6 min read

This article is part of #ServerlessSeptember. You'll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles from community members and cloud advocates are published every week from Monday to Thursday through September.

Find out more about how Microsoft Azure enables your Serverless functions at https://docs.microsoft.com/azure/azure-functions/

Introduction

At Zegami, we use Hubspot to track relationships with actual and prospective customers. But the process of provisioning new trial user accounts on our cloud platform was completely disconnected from our CRM. What was missing was a simple way to automate all of this so that our sales team could manage the entire process without getting the core development team involved.

Our initial plan was to create a separate backend administration portal for creating user accounts, but our sales team were reluctant. They live and breath Hubspot and didn’t want yet another silo of customer information to manage. So the task then was to use Hubspot as the entrypoint for user signups, and to create an automated process for provisioning accounts when users register through the Hubspot form on our Wordpress website.

Connecting Hubspot to our Admin API

Our API has an admin endpoint to provision user accounts. This requires a bearer token for the action to be authorised.

Hubspot’s workflows allow us to trigger a webhook whenever a new user signs up.

So can we just trigger our endpoint from Hubspot? No. There is no option to configure the webhook request made by Hubspot. We wouldn’t be able to supply the required token, nor shape the payload to match the schema the API expects.

One option would be to add a new endpoint to our API to handle the webhook. However, doing this would add surface area to our otherwise streamlined API, and introduce unwanted coupling to Hubspot.

Instead, we chose to implement a simple bridge to receive the webhook triggers and issue API requests. Azure Functions was the perfect way to host this lightweight, specific service.

Azure Functions

To create our Azure function, we made extensive use of the Azure Tools for Visual Studio Code. These tools are indispensable for painlessly setting up a function app, from initial setup through to local testing and deployment.

The first choice to be made was which language to use. Azure functions supports a number of languages we are familiar with at Zegami, namely Javascript, Python and C#. Because the primary language for Microsoft technologies is C#, some Azure Functions features are available in that language first, or have a more complete implementation.

We nevertheless selected Javascript since it’s the language in which we have the greatest fluency, especially when dealing with asynchronous flows and networking. All of the functionality we required was supported, but we would recommend confirming current language support for features if choosing something other than C#. We have previously found that some more advanced triggering and binding features aren’t available in the same way when using Javascript.

Setting up

To get up and running, we simply followed the excellent guide provided by Microsoft.

Writing the function

The function itself is very simple. We just need to extract relevant fields from the webhook request body and issue a request to our own API.

module.exports = async function (context, req) { 
  body = req.body; 

  // Get relevant details from body 
  const email = body.properties.email.value; 
  const firstname = body.properties.firstname && req.body.properties.firstname.value; 
  const lastname = body.properties.lastname && req.body.properties.lastname.value; 
  const name = `${firstname} ${lastname}`; 
  const company = body.properties.company && req.body.properties.company.value; 

  // Construct body for request to our API 
  payload = {name, email, company}; 
  body = JSON.stringify(payload); 

  // TODO - Execute request to our API 

  // Set webhook response details 
  context.res = { status: 200 } 
} 

Now we need to fill out our TODO to actually send the request. Node.js has a built-in http module, but we decided we would prefer to use the node-fetch module for a neater syntax and full consistency with our other code.

Having followed the guide, the function app already comes with a package.json file, so we simply run npm install node-fetch, then the package is available.

We now import node-fetch at the top of the file:

const fetch = require(‘node-fetch’);

And replace our TODO with the following:

const signupUrl = process.env.API_SIGNUP_URL; 

const response = await fetch( 
  signupUrl, 
  { 
    method: 'POST', 
    body: signupBody, 
    headers: { 
      'Content-Type': 'application/json', 
    }, 
  },
); 

There’s one more piece to call out above, which is the origin of the signup URL. Function apps allow environment variables to be specified externally, which are then available at runtime. These are managed through the Azure Portal, or through the ever-helpful Azure tools within Visual Studio Code.

This system allows secrets to be managed outside of source control, and also enables different configurations for local testing or staging/production instances.

N.B. For simplicity I’ve skipped over some other important details such as handling an error response appropriately, and verifying the authenticity of the incoming message.

Local testing

With our code written, we are now ready to ensure it behaves as expected. Yet again, Visual Studio Code proves indispensable here, making this extremely straightforward, with debugging tools to rival those we are familiar with in Google Chrome.

Step 1: Run the function app through the debugger

Alt Text

Step 2: Execute the function

Alt Text

The desired body with which to execute can then be provided:
Alt Text

Helpfully, it’s also possible to provide a sample.dat file to provide the default body for each test request.

Integration validation with ngrok

Our function seems to work, but we have made some hard assumptions about the data being sent by Hubspot.

I’m never fully satisfied until I’ve seen my code working with real-world data, and ideally had a little step through to validate my assumptions.

To do this, I used a utility called ngrok to open a tunnel which would make my locally running function available where real-life Hubspot could trigger it. Simply by running the following:

npm install –g ngrok 
ngrok http 7071 

My local environment is now available at a temporary, randomly generated public url. Using this approach it was possible to verify the flow from the point of filling the form on the website to where it came through this new function. Among other things, this made it possible to see exactly what payload the Hubspot webhook trigger provides, and debug any unexpected issues with the call to our main API.

Devops

We now have a working function, and all we need to do is deploy. Fortunately, yet again, Visual Studio Code makes this a trivial procedure and with just a few clicks our function is deployed to our Azure subscription. This process is described in the documentation linked above.

We don’t quite stop there however. Even though this is a small piece of code, it’s an important one for us, so we manage its full life-cycle as carefully as we do our other services.

  • Linting
    • All code must be added to master via a pull request
    • We have set up Azure Pipelines to automatically run code against our eslint rules. This keeps style consistent with all our other Javascript code.
  • Deployment
    • However convenient, we don’t really want a human operating the VS Code GUI every time we want to deploy. Fortunately, the Azure CLI makes it simple to automate this in a way that integrates with the rest our deployment process which is centered around Jenkins.
    • It’s worth noting that node modules must be installed on the environment from which the deploy is triggered. The entire node_modules folder is copied to Azure upon deployment.
  • Monitoring
    • We use Grafana to monitor all of our production services. The excellent Grafana Azure plugin has made it incredibly easy to add a dashboard for this new service to show us usage stats and alert us of any errors.

There we have it. With a relatively small amount of effort, we’ve bridged our CRM system to our API. This system has been working very well since we rolled it out, and we are already looking for other opportunities to use Azure Functions to rapidly develop and deploy functionality.

Discussion (0)

pic
Editor guide