How to Automatically Associate New Tickets with a Recent Deal

As new tickets come in, it can be helpful to know which tickets are assigned to what revenue. Sadly, HubSpot tickets do not currently automatically become assigned to the most recent deal for a client.

Using the Operations Hub in HubSpot, I built a script in Python that allows you to automatically associate every new ticket to the most recent deal for the company assigned to it.

Here is the step by step process on setting up this script and workflow:

An Introduction to Operations Hub Professional

Operations Hub Professional is the most recent HubSpot product release. The exciting opportunity that is included in this product is that you can use custom code directly inside of HubSpot.

With this new feature, you can create functionality that does not currently exist in HubSpot.

The only issue is that learning to custom code with Python can take time.

That's why I've built these scripts and tutorials so you can leverage this functionality with a couple of copy and pastes.

The Problem

Currently, when a ticket comes in from a customer, there is a company and contact attached; however, no deals come in automatically.

This makes it difficult to match ticket support with a specific SLA, product line, or deal. This means that you can't track time by team members, costs to fulfill, or service level to a specific agreement.

For my organization, I run a HubSpot consultancy that processes tickets for monthly retainers with specific time hours assigned to them.

By assigning a ticket to a deal, I can see the cost and profit of tickets per agreement. This is perfect for project tracking. Now, I have a direct route to assign effort to revenue.

The Solution

The solution I came up for my organization is to create a python script inside a custom code workflow that automatically find the most recent closed deal from a company and creates an association between the most recent closed deal and the new ticket.

With this workflow, a ticket can find this data as it comes in and you can start tracking effort directly to revenue.

The Benefits

With this script, you will be able to track the following metrics in HubSpot:

  1. How much employee time was spent on a specific deal to fulfill it
  2. How long it took to fulfill a deal from start to finish
  3. How much cost and profit a deal generated
  4. What the level of the service was provided for this deal

Without further adieu, let's talk through this script.

Feel free to follow along with the video below as you read:

Create the Python Script

This workflow leverages the power of Python to attach a new ticket with the associated deal of the company requesting help.

Let's walk through how this script works.

Import Your Python Libraries

First, you need to import your libraries. We will use requests to call the HubSpot API and use the os library to pull our private token securely from HubSpot.


# First, import the libraries you need
# You need the requests library in almost every Python script
import requests
# You will need the os library so you can pull your private token securely.
import os

Create Your Authorization Headers

Next, you will need to pull the private token from your variables in HubSpot and store it in the headers we will use for all of our requests.


# Pull the "Private Token" variable from the environment to keep your private key secure
private_token = os.getenv('PRIVATE_TOKEN')
headers = {"accept": "application/json","content-type": "application/json", "Authorization": "Bearer " + private_token}

Create a Function that Pulls all Properties of an Object

Now, we are starting to enter the meat of the script. We need to create a function that allows us to pull all the property names of an object. This will allow us to easily pull any property from an object in the code for the future.


# Define a function that pulls all the properties of a specific object
# This string can take in the following options:
# contacts, companies, deals, tickets, marketing_events, line_items, etc
def get_property_list(object_type):
    # Create a URL variable that takes in the specific object through the property V3 API
    url = "https://api.hubapi.com/crm/v3/properties/" + object_type + "?archived=false"
    # Send a GET url to the HubSpot API with our Authorization Headers
    response = requests.get(url=url, headers=headers)
    # Retrieve the results from the response
    results = response.json()['results']
    # Create a list of all the names from the properties
    property_list = [x['name'] for x in results]
    # Return the list of properties to the part of the code that called the function
    return property_list

Define a Function that Finds a Ticket's Company ID

Next up, we need to find the company id for the ticket in question. This is accomplished by creating a new function that pulls the primary company of the ticket using the association API.


# Define a function that takes in the ticket id and returns the primary company id with all its properties
def get_company_of_ticket(ticket_id):
    # Create a URL variable that holds the V1 Association API with our ticket ID variable and the association type for Ticket to Company
    url = 'https://api.hubapi.com/crm-associations/v1/associations/' + str(ticket_id) + '/HUBSPOT_DEFINED/26'
    # Send a GET request to the API with our authorization headers and store the response in a response variable
    response = requests.get(url=url, headers=headers)
    # Store the JSON values in the company results variable
    company_results = response.json()
    # Pull the first company object in the list of companies returned
    # Wrap this in a try, except in case there aren't any companies attached
    try:
        # Store the first company object in the company variable
        primary_company_id = company_results['results'][0]
    except IndexError as e:
        # Return nothing if there isn't a company attached
        return {}
    # Create a list of all the properties of companies to pull all properties from HubSpot for the primary company
    property_list = get_property_list('companies')
    # Create a property query to send to HubSpot through the URL so you can get all of the data
    property_query = "?properties=" + '&properties='.join(property_list)
    # Create a URL variable to use the V2 Company API to pull the properties for this primary company object
    url = "https://api.hubapi.com/companies/v2/companies/" + str(primary_company_id) + property_query
    # Send a GET Request to the HubSpot API with our Authorization header
    response = requests.get(url=url, headers=headers)
    # Store the response results in the company_property_results variable
    company_property_results = response.json()
    # Return the data to the part of the code that called the code
    return company_property_results

Create a Function that Finds the Deals of a Company

The last standalone function we need to create is one that pulls all the deals attached to a company along with its properties. This is accomplished using the association API. We first get all of the "company to deal" associations.

Next, we batch read all of the deals that are associated with the company and pull the following properties: close date, whether it's closed won, and it's deal stage. With this data, we can filter the deals and sort them in our main function which comes next.


# Define a function that takes in the company id and returns the deal ids attached to it with all its properties
def get_deals_of_company(company_id):
    # Create a URL variable that holds the V1 Association API with our company ID variable and the association type for Company to Deals
    url = 'https://api.hubapi.com/crm-associations/v1/associations/' + str(company_id) + '/HUBSPOT_DEFINED/6' + "?archived=false&limit=100"
    # Send a GET request to the API with our authorization headers and store the response in a response variable
    response = requests.get(url=url, headers=headers)
    # Store the results from the response as list of IDs called deal_ids
    deal_ids = response.json()['results']
    # Create a URL variable that holds the V3 Deal Object API with our deal IDs variables
    url = "https://api.hubapi.com/crm/v3/objects/deals/batch/read?archived=false"
    # Create an array to hold the JSON data for our IDs to send to HubSpot deals
    deal_id_inputs = []
    # Iterate through all the deal IDs and add to the our input list in the format that HubSpot's API accepts
    for deal_id in deal_ids:
        # Append a new dictionary value to the list
        deal_id_inputs.append({
            "id": str(deal_id)
        })
    # Create a new data object in the format that HubSpot accepts to read all the deals
    data = {
        "properties": [
            # Pass the properties we need to pull to filter by won deals and most recently closed deals
            "dealstage", "hs_is_closed_won", 'closedate'
        ], "propertiesWithHistory": [],
        "inputs": deal_id_inputs
    }
    # Send a POST Request to the HubSpot API with the deal ids and the authorization headers
    response = requests.post(url=url, json=data, headers=headers)
    # Store the results of the response in a variable
    deal_property_results = response.json()
    # Return the data to the part of the code that called the code
    return deal_property_results

Create a Main Function that Associates the Ticket with the last closed deal

Finally, we will create a main function that uses the other functions to associate a new ticket with the last closed deal of a company.

In this function, we will do the following:

  1. Receive the ticket ID from HubSpot
  2. Find the company ID for the ticket
  3. Get every deal associated with this company
  4. Filter only the closed deals
  5. Sort the deals by most recent close date
  6. Associate the ticket with the most recent deal that has been closed

# Create a new function called "main" that receives the "event" object from HubSpot.
def main(event):
    # Pull the ticket ID from the input. The variable will be stored as "hs_ticket_id"
    ticket_id = event.get('inputFields').get('hs_ticket_id')
    # Use the function defined above to pull the company from the ticket ID
    company_id = get_company_of_ticket(ticket_id)['companyId']
    # Use the function defined above to pull the deals from the company of the tickt
    deals = get_deals_of_company(company_id)['results']
    # Filter the deals by those that are already closed won
    closed_deals = [deal for deal in deals if deal['properties']['hs_is_closed_won'] == 'true']
    # Sort the deals by how recent they were closed
    most_recent_deals = sorted(closed_deals, key=lambda d: d['properties']['closedate'], reverse=True)
    # Check whether any deals were returned
    if len(most_recent_deals) > 0:
        # If there are recent deals, run the following code to create an associate for the first deal in the data
        # Create a URL variable for the association API
        # We're using the V1 API because it's simpler and can process one association easier
        association_url = "https://api.hubapi.com/crm-associations/v1/associations"
        # Pull the first deal ID from the data. This will be the most recent closed deal because we sorted it previously
        most_recent_deal_id = most_recent_deals[0]['id']
        # Create your JSON Data to create the association. This is the data you're sending to HubSpot's API.
        json = {
            # Put the ticket ID below with a variable
            "fromObjectId": ticket_id,
            # Put the deal ID we pulled earlier
            "toObjectId": most_recent_deal_id,
            # Specify that this is a HubSpot defined Association
            "category": "HUBSPOT_DEFINED",
            # Set the "Ticket to Deal" association type number below. To change this code for other objects, you will need different numbers.
            "definitionId": 28
        }
        # Send a PUT request to the association URL with your authorization headers and the JSON data.
        response = requests.put(url=association_url, headers=headers, json=json)
        # Receive the status code from the response. It will be 204 if it is a success.
        status_code = response.status_code
        return status_code
    else:
        # Return an error code if there are no deals attached to this company
        return 404
  

Create the Custom Code HubSpot Workflow

Next, we need to create our workflow so this code triggers every single time a new ticket comes in.

From the main menu, click on the "Automation" tab and then click on the "Workflows" option.

You will want to click the orange "Create workflow" button in the top right and click on "From scratch". This will create a new blank workflow.

Next, select the "Ticket-based" option on the left-hand column. Select the "Blank workflow" radio button and click the orange "Next button" on the top right.

Click on the "Trigger" area at the top of the workflow which will open up the trigger options on the righthand side.

Select the "Ticket" filter option on the enrollment triggers.

Since this workflow requires a company on the ticket, look for the "Number of Associated Companies" property on the ticket and select it.

Set the filter to check if the number is greater than 0. This will only trigger this workflow when a ticket has a company to search for which will reduce potential errors. Click the "Apply filter" button and move to the next step.

Next, click the plus button underneath the trigger to add a new action. This will open actions on the right hand side that allows you to determine what this action does.

Click on the "Custom code" option as an action.

Change the language from the default Javascript to Python.

Add in your Private Token secret. It will typically look like PRIVATE_TOKEN. The code below is setup to use this name so make sure you change out the name if you name your private token different.

Add your ticket id as a property to pass into the code as "hs_ticket_id". This variable is a critical name for the code to work.

Finally, copy the script into the workflow code area. You can download the script below.

Next, click on the "Review and publish" orange button in the top right.

Set the workflow to only apply to tickets going forward by selecting the "No" radio button. If you use Yes, it will throw a lot of errors as these custom code pieces aren't designed for backlogging and queueing natively.

Your final workflow should look like this.

Now, all of your new tickets with an existing company will pull the last closed deal from the company and associate the ticket with that deal.

Download the Script

With this script, you can start tracking ticket data with deals inside of HubSpot.

Download the script by filling out your information below.

Make sure to test it on a single ticket before turning on to make sure you don't have errors specific to your HubSpot account.

If you need assistance, I'm happy to talk through the problem with you. Reach out to my LinkedIn.

 

Your CRM Should Multiply Your Company's Success.

Your CRM determines how efficient your revenue team can be.

  • Imagine your sales cycle dropping quarter by quarter
  • Imagine your return on ad spend skyrocketing
  • Imagine your churn dropping without any new feature updates

All of this is possible with a powerful CRM and a partner you can rely on.