This blog post is part of our 5-part series about HubSpot API Features. In case you missed Part 3, check it out here!
In this fourth installment of our HubSpot API series, we’ll learn how to create a front-end widget for our external data that lives directly on your HubSpot records. In other words, any non-HubSpot sourced data can be pulled in and displayed onto a contact, company, deal, or ticket record without actually living in the HubSpot database (we’ll get into why storing data this way can be particularly useful later). These displays can also be built so that the user can interact with the external data from the HubSpot record itself.
These widget displays, or Custom Cards, are configurable via the API and UI as part of an OAuth’ed HubSpot app. In this article, we’ll walk through how to create and install a card app onto a HubSpot account, from scratch, with both methods. And if you’re not familiar with what these apps are or how they work (or just need a refresher), don’t fret – we’ll review them first before digging into the configuration.
What are Custom Cards?
Cards are external data widgets that live on HubSpot CRM records, which can be viewed by users. When a card app is installed in a HubSpot account, it’s associated with one or more default objects (contacts, companies, deals, and/or tickets). When a user views one of these object records, HubSpot will send an outbound request to an endpoint that you specify. The middleware layer responsible for that endpoint needs to be configured to handle that request and respond with data in a format that HubSpot expects. If successful, the data your middleware responds with will be displayed on an association card on the record:
Image sourced from developers.hubspot.com
That’s pretty cool and can be useful in a number of cases, but the real magic lies in the interactableness (it’s a word now! 😉) of the widget. Not only can you view external data, but you can build your app to allow users to interact with the external source right from the card in HubSpot. Common actions include: adding data to items, deleting items, notifying users in the external system of something… really anything that makes sense for your use case can be configured.
Example Use Case for Custom Cards
Before we put fingers to the keyboard, let’s review an example use case that we’ll follow throughout the duration of the tutorial to help contextualize everything.
Let’s say our sales team uses HubSpot to prospect and close service-based deals. Once a deal is won, our project team gets started on fulfilling that service and uses another third-party tool to track the progress of the project. Back in HubSpot, our sales team likes to follow up with these new clients after a certain period of time, to check in on status and try to upsell with additional services.
Let’s assume our sales team wants to see the progress of a project on a given deal within the HubSpot tool (so they don’t have to system-hop or reach out to the project team). This data doesn’t need to live in HubSpot though – that is, the data needs to be viewable but not necessarily exist as record-level data.
Custom cards can be created to do exactly that – a project’s progress and other useful information can be displayed right on a deal record to provide the sales rep with more insight into the project.
App Infrastructure
Now that we understand why we’re building a card app, let’s take a look at how we’ll be building it. As we mentioned earlier, once a card app is installed on a HubSpot account, whenever a user views a card on a deal record, HubSpot will automatically send an outbound request to our middleware layer. That middleware layer must respond in a specific format that HubSpot can understand and display on the card.
That said, we’ll need to build a piece of middleware that sits between HubSpot and the project management tool backend. We’ll use Python’s Flask framework to handle the requests to and from HubSpot, as it’s lightweight and easy to use, and Ngrok for test connection, but feel free to use whatever toolset you like. Here’s the flow we’ll ultimately need to follow:
User clicks into a Deal record → HubSpot sends request to our Flask endpoint (to be created) → Flask searches our fictional project backend by HubSpot Deal ID and returns its data → HubSpot displays said data on the card
App Authorization
In order to configure custom cards, you’ll need to properly create and authenticate a developer app with your target HubSpot account. HubSpot uses the OAuth 2.0 grant type protocol, a specific authorization flow the app must follow.
This flow first requires the app to launch a browser window to send the user to HubSpot’s OAuth server, where the user approves the request in the browser by selecting the HubSpot portal in which to install the app. The user then receives an authorization code back in the app, which needs to be exchanged for an access token. So, in order, this flow requires 4 steps:
- The app opens a browser window to send the user to HubSpot’s OAuth 2.0 server
- The user grants permission to the app and selects the HubSpot portal to connect the app to
- The user is redirected back to the app with an authorization code in the query string
- The app exchanges that authorization code for an access token
Here’s a detailed walkthrough of this process that you can follow to get the app installed.
Once installed, you’re ready to start building!
Create a Card in HubSpot
Now that we’ve covered what a card app is, how it works, and how to install it, we can (finally) start building. We have our app created in our developer account and installed in our HubSpot instance (we recommend starting with a sandbox or test account before installing anything in production), so we’ll now add the Custom Card functionality to the app.
Frontend
The card setup can be done on the frontend by clicking into your App in your Developer Portal and then clicking CRM cards under the Features section. From here, click Create CRM card and name it Projects.
1. Data request
Our card app will be deal-based, so in the Data request tab, we’ll want to click the switch on the Deals Record Type. We’ll also choose hs_object_id as the property HubSpot will send to our Flask middleware – this will send the deal ID as a query parameter to our endpoint.
For the Data fetch URL, we’ll want to drop in the Flask endpoint that will handle the main process logic (more on this in the next section). Ultimately, this app would be deployed through a web service (we like Heroku) but for testing purposes, we’ll use Ngrok, which will provide us with a temporary URL.
You can leave this blank for now – we’ll be spinning up an Ngrok URL and dropping it here a bit later.
2. Properties
Next, navigate to the Card properties tab to create the data points to display on the card. We’ll create three:
project_status, due_date, and project_type with the following field types: status, date, string, respectively. Status fields are essentially picklist fields with predefined color blocks – we’ll give our field three options: in_progress, complete, not_started.
3. Custom actions
As we mentioned earlier, we can also configure our app to not only display our external data but be able to interact with our external data as well. While we won’t cover how to do this in this tutorial, it might make sense for this use case to configure the cards to have a button that the sales reps can click on and mark when follow-up for that deal/project was done, which would then display on the card. Take a look at the full HubSpot documentation to learn how to set this up.
Back-end
Alternatively, this setup can be done on the backend, via the API. Check out HubSpot’s documentation for request examples here.
Flask Configuration
Again, our Flask middleware app will need to catch the outbound request that HubSpot sends whenever a user views a card on the UI and then return data to display on the card. We’ll want to create two Python files; one to handle the main routing logic (webhook handling) and another to handle the lookups and returns. Let’s write the latter first so we can call it into the route file.
For the sake of simplicity, we’re going to represent the project management tool backend as a local SQLite database, called card.db. In the database, we have a simple table called project, which looks like this:
id | objectId | title | project_status | due_date | project_type |
1 | 9682825112 | OBO Integration Project | in_progress | 2022-12-31 | integration |
2 | 9682825112 | OBO Consulting Project | complete | 2022-07-01 | consulting |
Make sure that the values in the objectId column are actual deal IDs in your sandbox/test HubSpot instance.
Next, in a file called projects.py, we’ll write two functions; one for connecting to the SQLite database and another to take in a deal ID, search the table for rows with that deal ID, and return an object that we’ll drop into the response in our to-be-created routing file.
import sqlite3
def connection():
# connection object
conn = sqlite3.connect('card.db')
# set row factory on connection object (returns dict rows)
conn.row_factory = sqlite3.Row
# cursor object
cur = conn.cursor()
return conn, cur
def return_projects(deal_id):
conn, cur = connection()
# find rows in project table by deal_id
cur.execute("""SELECT * FROM project where objectId = '%s'""" % deal_id)
rows = cur.fetchall()
# append dict rows to list
results = []
for row in rows:
results.append({k: row[k] for k in row.keys() if 'id' != k}) # if block excludes table id key/val
print(results)
# close the connection
conn.close()
return results
Here’s what that returned object would look like were we to print it:
[{'objectId': '9682825112', 'title': 'OBO Integration Project', 'project_status': 'in_progress', 'due_date': '2022-12-31', 'project_type': 'integration'}, {'objectId': '9682825112', 'title': 'OBO Consulting Project', 'project_status': 'complete', 'due_date': '2022-07-01', 'project_type': 'consulting'}]
As you’ve probably noticed, we made things simple here by naming the table columns the same as the card fields, which greatly simplifies the return logic. In a true real-world scenario, this will more than likely be a bit more involved. Check the full HubSpot documentation for specifics on how to format the response.
With that noted, let’s now create a file called app.py to handle the actual routing. Here, we’ll:
- Import the Flask package along with our projects.py file
- Initialize the Flask instance
- Create a route and function to catch the webhook, parse the deal ID from the querystring, and send the deal ID into our return_projects function
- Set our port for local testing
from flask import Flask, request
import projects
app = Flask(__name__)
@app.route('/card')
def card():
deal_id = request.args['associatedObjectId']
db_results = projects.return_projects(deal_id)
res = {
"results": db_results
}
return res, 200
if __name__ == '__main__':
app.run(debug=True, port=9999)
We’re returning the object along with a 200 code if successful.
App Testing
Now that we have our code written, let’s run our app to make sure it’s working properly. In a terminal, navigate to your project directory and make sure Ngrok is installed there. You can enter the following command: ngrok -v to double check (and of course, see the version).
To deploy an Ngrok instance, enter the command ngrok http 9999 (we chose 9999 as the port number in the app.py file). An http and https URL will spin up and display in the terminal – go ahead and copy the https URL to your clipboard.
Next, head back to your HubSpot app and drop that URL postfixed with /card into the Data fetch URL box in the Data request tab (as we walked thru earlier), and save:
Now, run your app.py file. You’ll see something like this printed to the terminal:
* Serving Flask app app (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:9999/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 634-508-112
This means that the app is up and running and ready to listen for HubSpot webhooks.
Head on over to your HubSpot sandbox or test instance and click into a deal record with an ID found in the SQLite table. If everything was set up correctly, you’ll see a card app with data pertaining to the deal’s project:
Woohoo! You just created your first card app! Pretty easy right?
Final Thoughts
And with that, we’ve created an automated process to increase the efficiency of the upsell process for sales reps. Project status(es) are clearly displayed on a closed deal’s record, providing the rep with key follow-up information without having to system-hop or manually check in with the project team.
Note that we were able to quickly create and install a card app with minimal code, but keep in mind that our use case was simple in nature and is by no means exhaustive in terms of functionality. We didn’t tutorial the “action hooks” feature, which allows you to create buttons on your card for users to interact with the data and backend, as this is a bit more involved but we implore you to learn more about further customizations through HubSpot’s documentation.
So to recap this tutorial, we:
- Created and installed a custom app
- Configured the card setting
- Built a lightweight Flask middleware layer
- Test deployed our app with Ngrok
Now you can take what you’ve learned here and apply it to a full scale application! And as always, feel free to drop us a line if you’re interested in collaborating on an app build or any of our other services.