Implementing Business Logic With HubSpot Workflow Automation & Custom-Coded Actions

'Implementing Business Logic With HubSpot Workflow Automation & Custom-Coded Actions' text on yellow background of a Workflow screenshot
Implementing Business Logic With HubSpot Workflow Automation & Custom-Coded Actions
6:55

Hello, everyone! This is Zachary Lyons, integrations developer at Hypha Development, and I’m thrilled to be sharing my knowledge and expertise with you!

So we’ve recently been leveraging HubSpot’s workflow automations for many of our clients. As the name suggests, workflows automate tasks that you would otherwise have to delegate to someone on your team. Let’s say your team needs to set reminders to touch base with a client every two weeks—workflow automation can do that. Or maybe you need to periodically check on the status of a ticket or lead and update its pipeline stage depending on some detail in the record—workflow automation can do that, too!

There are even more complicated use cases that can be accomplished through automation. For those, you can leverage custom-coded actions in the workflow to get even more hands-on with the data in your CRM. 

In this article, I’ll be breaking down one such case we recently encountered with a client: a manufacturing shop that produces custom parts, sometimes manufacturing the same part for different customers. They needed a workflow to determine which of their customers takes priority, and allocate any available quantity for the part to the first in line. We’re going to cover how to set up a blank workflow, trigger to enroll the right records, and custom code block to get hands-on with the data!

Note 1: If you’re unfamiliar with custom-coded actions, I strongly recommend checking out this recent post first: Unlocking the Power of HubSpot Custom-Coded Workflow Actions 

Note 2: Using custom-coded actions in your workflow automation requires a subscription to Operations Hub Professional.

Setting Up Your Workflow

A screenshot of the create workflow button in hubspot


First, we’ll need to create our workflow. From HubSpot’s dashboard, navigate to Automations > Workflows. From there, select Create workflow > From scratch—and boom, we’ve created our workflow! 

Now to configure what records get enrolled by setting up our workflow’s trigger.

Trigger

Workflow enrollments trigger screenshot

Your trigger controls which records will flow into the automation. HubSpot offers many options for filtering and triggering the workflow. For our case study, we’ll be enrolling a custom object “Parts” that our client uses to track their manufacturing projects. 

Our workflow is focused on handling the allocation of available parts to different customers, so we’ll trigger the automation when a Parts’ “Quantity Available” property is updated. 

Another piece of business logic to consider is that we only want to allocate available Parts to projects that are still active, so we’ll add a filter that the Part is in the pipeline stage “In Production.” 

Now, we’ll add another step to the workflow with our custom-coded action.

Adding Custom-Coded Action as a Step

Workflow builder configuration step screenshot

Below the enrollment trigger step, you’ll see a plus icon that enables you to choose an action for the next one. We’ll select Data ops > Custom code. 

Now we’ve done all the work to set up our workflow, and can begin to code!

Implementing Custom Code

Our specific use case will have us fetch records from the HubSpot CRM Search API, compare values in those records, and then update them again via the HubSpot API. Our client’s use case had the following parameters:

  • We needed to take any available quantity for the Part in question and allocate it to the customer with the soonest shipping date. 
  • If there are two Parts of the same type with the same shipping date, the Part with the oldest creation date should get priority. 
  • Lastly, we’ll need to prioritize Parts that have the “Expedite Shipping” custom property set to true. 

Let’s dive in with fetching data!

Fetching Records via API

Property to include in code section screenshot from HubSpot

const axios = require('axios');

exports.main = async (event, callback) => {
  const partNumber = event.inputFields['number'];
  console.log(`> partNumber to process: `, partNumber);

Before we fetch all Parts with the same “Part Number” property, we’ll need to import it into our code. Use the “Property to include in code” menu to add it to the event object’s input fields. 

Next, you’ll need to assign it to a variable (See: console.log(`> partNumber to process: `, partNumber);) so we can read the value and use it with HubSpot’s search API to find all Parts with the same numbers.

async function searchForPartsWithMatchingPartNumberRequester(partNumber){
  const data = {
    "properties": [
      "order_quantity",
      "quantity_available",
      "expected_ship_date",
      "hs_pipeline_stage",
      "expedite_shipping_"
    ],
    "filterGroups": [
      {
        "filters": [
          {
            "propertyName": "number",
            "operator": "EQ",
            "value": partNumber
          },
          {
            "propertyName": "hs_pipeline_stage",
            "operator": "EQ",
            "value": "152121737"  // ID for pipeline stage "In Production" 
          }
        ]
      }
    ]
  };

  const config = {
    method: 'post',
    maxBodyLength: Infinity,
    url: 'https://api.hubapi.com/crm/v3/objects/p_parts/search',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.KRYTON_TICKETS_API_KEY}`
    },
    data : data
  };

  return axios.request(config)
  .then(response => response.data)
  .catch(error => {
    throw error;
  });
}

HubSpot includes axios—used to make HTTP requests to HubSpot’s APIs—as a dependency in all custom-coded actions. (It just requires you to include const axios = require(‘axios’) at the beginning of your code block.) 

In the code block above, we can see the “data” object definition that holds all of our search parameters. We’re asking for the Parts properties we want included with our results, and “filterGroups.” The filter groups property enables us to define what Parts we want returned from our search. 

Here, we say we want all Parts matching the given “partNumber” property and those in the “In Production” pipeline stage. This will return a list of Parts records that match our search criteria! 

Comparing Values

function sortPartsByCreateDateAndShipDate(partsList){
  console.log(`> Sorting Parts by Expected Ship Date, Create Date, and Expedited Shipping state`);
  const sortedList = partsList.sort((partA,partB)=>{ // prioritize expedited shipping parts
    if(partA.expedite_shipping_ && partB.expedite_shipping_){ // if both parts are expedited then compare create and ship dates
      if(partA.expected_ship_date === partB.expected_ship_date){ // if expected ship dates are the same compare create date
        const dateA = new Date(partA.hs_createdate).getTime();
        const dateB = new Date(partB.hs_createdate).getTime();
        return dateA - dateB;
      }
      else{ // otherwise compare expected ship date
        const dateA = new Date(partA.expected_ship_date).getTime();
        const dateB = new Date(partB.expected_ship_date).getTime();
        return dateA - dateB;
      }
}
else if(partA.expedite_shipping_ && (!partB.expedite_shipping_)){  // if part A is expedited and part B is not, prefer part A
return -1;
}
else if((!partA.expedite_shipping_) && partB.expedite_shipping_){  // if part A is not expedited and part B is, prefer part B
return 1;
}
    else{ // If neither part are expedited shipping compare expected ship date and create date
      if(partA.expected_ship_date === partB.expected_ship_date){  // if expected ship dates are the same compare create date
        const dateA = new Date(partA.hs_createdate).getTime();
        const dateB = new Date(partB.hs_createdate).getTime();
        return dateA - dateB;
      }
      else{ // otherwise compare expected ship date
        const dateA = new Date(partA.expected_ship_date).getTime();
        const dateB = new Date(partB.expected_ship_date).getTime();
        return dateA - dateB;
      }
    }
  });
  return sortedList;
}

Next, we’ll need to sort the Parts records so those with the highest priority are at the top. We’ll pass our “partsList” array of records into the sorting function and leverage JavaScript’s array.sort() method, which accepts a sorting function as a parameter. Our sorting function will first compare whether any two Parts have expedited shipping, giving preference to those expedited. 

Then, we compare their expected ship date, and lastly, their create date, if needed. This returns a list of Parts sorted by priority. Now, to allocate and update the records.

Allocating Available Quantity

function subtractPartsQuantityFromAvailablePartsHandler(partsList){
  try{
    let updatedParts = partsList;
    for(let i = 0; i < updatedParts.length; i++){
      if(updatedParts[i].order_quantity < updatedParts[i].quantity_available){
        // change pipeline stage hs_pipeline_stage = 152077989
        updatedParts[i].hs_pipeline_stage = "152077989";
        // subtract order quant from all parts quant available
        updatedParts.forEach(part => {
          part.quantity_available = part.quantity_available - updatedParts[i].order_quantity;
        });
      }
    }
    return updatedParts;
  }
  catch(error){
    console.log(`> There was an error processing Parts and subtracting Order Quantity from Quantity Available`);
    console.log(partsList);
    throw error;
  }
}

Next, we need to loop through the sorted list and allocate the available quantity to Parts. You can see above how we use a “for” loop to count through the list of Parts, subtracting the needed “order quantity” from the “quantity available” properties. Once we’ve allocated the quantity needed, we can change that Parts pipeline stage to “Ready to Ship” using HubSpot’s internal ID for the stage. 

Now that we’ve finished processing our Parts, we can update them in HubSpot.

Updating Records via API 

async function updateHsPartsRequester(parts){
  const data = { "inputs": parts };
  const config = {
    method: 'post',
    maxBodyLength: Infinity,
    url: 'https://api.hubapi.com/crm/v3/objects/p_parts/batch/update',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.HS_TICKETS_API_KEY}`
    },
    data : data
  };

  return axios.request(config)
  .then(response => response.data)
  .catch(error => {
    throw error;
  });
}

As a last step, we’ll take our updated Parts records and pass them back to HubSpot. We’ll be using the v3 API’s Batch update endpoint to update all records in one request. 

With this, we’ve finished our work: We’ve outlined setting up the workflow automation, importing properties from the enrolled record, fetching records, processing them, and updating them in HubSpot. 

Hopefully, this use case can inspire you to get more hands-on with the data in your Hub. Automating similar tasks frees up human resources your team needs to do the important work, so make use of it!

Related Content: Enhancing Manufacturing Efficiency With HubSpot Custom-Coded Workflow Actions


Are you in need of custom integration for your business? Strategic content? Impactful design? Contact Hypha HubSpot Development today for all your marketing, website design, and integration needs!