Using HubSpot’s Custom CRM Cards Without an Integration

Recently updated on

The marketing software company HubSpot offers a nifty feature called "custom CRM cards."  The idea is that a HubSpot dashboard can send a GET request to an API that you maintain, retrieve some JSON-formatted information and render it in some "cards" alongside other data (fig. 1).

Figure 1

One of our clients wished to use this feature to pull in some data from their Django site without leaving HubSpot, and this turned out to be trickier than one might expect.  Although HubSpot's own documentation is well written, it assumes that the reader is familiar with certain other aspects of HubSpot.  For those new to HubSpot's features and products, as I was, it might not be enough to get you all the way to your goal.  In this article we will explore, from beginning to end, how to create the cards and how to make them render in your dashboard.

First and foremost, you will need access to a HubSpot account.  Assuming you have this, read on.

PART I - Creating the Card

The custom CRM cards aren't created as free-standing entities that can be configured entirely within your HubSpot dashboard; rather, they have to exist inside a HubSpot "app" that you create.  Creating an app requires a HubSpot developer account.  This is different from an ordinary HubSpot account, but you need the ordinary account to create the developer account inside it.  If you don't have one already, you can create one by going to

Once you've created the developer account, find your way to your developer account dashboard.  You should see options to "Manage apps" and "Manage test accounts" (fig. 2).

Figure 2

We're going to need both, but for now click "Manage apps," and then "Create app."  And that's it!  The app is now created.  Rename it if you want, but at this point you are ready to configure your custom CRM cards.

On the dashboard of the app you just created you should have a few choices in the left-hand nav.  We're going to need "Basic Info" and "CRM cards," but let's start by clicking "CRM Cards."

This part is pretty straightforward and is well documented at the link above.  However, I'll summarize the procedure here: Click "Create CRM Card."  This should bring up a UI with three tabs, "Data request," "Card properties" and "Custom actions."  Select the "Data request" tab (fig. 3).

Figure 3

In the field labeled "Data fetch URL" you'll enter the endpoint of the API you're going to make that will return the JSON used to populate the card.

Note: We're not going to discuss how to build an API here; that can be done by any method you choose.  All that matters is that the API be accessible over HTTPS at a fully qualified domain name, and that it return a JSON response with a couple of specific fields.  First, it must contain a property called "results" that is associated to a list of JSON objects (key-value pairs).  Second, each object in the list must -- at a minimum -- have properties called "objectId" and "title."  If either property is missing, the card will not render.  The structure of the JSON response is documented under the "Webhooks" tab at the HubSpot documentation URL above.

Below "Data fetch URL" is "Target record types," where we have a grid of three columns (fig. 4).

Figure 4

By flipping the switches in the column labeled "Appear on this type?" you determine the contexts in which your HubSpot dashboard will attempt to render the cards.  Go ahead and flip the switch in the "Contacts" row.  Now the cards will appear when you visit the "Contacts" area of your dashboard.  The third column of the grid is "properties sent from hubspot." This represents the information that will eventually be sent in the querystring of the GET request to your API. For now, just add "hs_object_id" in there. Now your display should look like figure 4 above.

That's everything we need to do on the "Data request" tab.  In fact, that's all we need to do on this page, but I'd like to briefly discuss the "Card properties" tab and how it can affect what gets rendered in the card.

When the CRM card fetches data from your API, it doesn't automatically render everything it receives in the response.  For example, your response might look like this:

      "results": [
          "objectId": 1,
          "title": "Test object one",
          "description": "The first test object"

The formatting is correct, with a "results" property followed by a list of objects (well, one object). Furthermore, each object supplies the required "objectId" and "title" properties, as discussed above.  This response is valid, and if you had everything else running, the "title" would be rendered in your card.  But presumably you'd like your card to tell you more than just a "title." For example, the object in this response also contains a "description" property.

Here's the trick though: properties other than "title" will not render in your CRM card unless you also declare them in the "Card properties" tab.  If you click on that tab and then click "add property," you can tell the card to look for a property named "description" with a data type of "string" and to display it with a label of your choosing.  Now the "description" property in your JSON response will be rendered in the card.  You can declare as many properties as you want in this way, and the order in which you declare them defines the order in which they will appear.

This arrangement sounds all right at first, but it has its drawbacks. First, you must manually declare each individual property, assigning it a label and data type, in order to render it in your card.  This quickly becomes tiresome.  Second, there is no easy way to reorder properties once you've declared them.  If you declare 10 properties and then decide you'd like property 4 to appear third instead of fourth, you'll have to delete your properties 3 - 10 and start over from there.

Luckily, there's a workaround: Don't declare your additional properties on the same level with "objectId" and "title."  Instead, put them into an inner object called, appropriately, "properties."  Anything in the "properties" object, if correctly declared, will render in the card.  Just make sure to supply a data type and label as well.  For example, we could get our "description" property into the card like this:

      "results": [
          "objectId": 1,
          "title": "Test object one",
          "properties": {
              "label": "Description",
              "dataType": "STRING",
              "value": "The first test object"

Some data types require additional information.  For example, monetary values require a "currencyCode" key:

      "results": [
          "objectId": 1,
          "title": "Test object one",
          "properties": {
              "label": "Price",
              "dataType": "CURRENCY",
              "value": "100.00",
              "currencyCode": "USD"

HubSpot provides documentation for all data types

This method of getting properties into your card requires more work on the backend and careful building of your JSON objects, but it allows you to add, change, or reorder properties however you like without needing to fuss with the "Card properties" tab.

PART II - Rendering the Card

I was stuck at this stage for a long time.  Creating the card and app had gone fine, but what now?  According to HubSpot's documentation, I needed to "install the app," which involved configuring OAuth and creating a "redirect URL."  Wait, what?  Why did I need OAuth when I'm using these cards in the HubSpot dashboard that I am already logged into?  Why did I need a URL that would redirect me to... where?  I'm already in my HubSpot dashboard, which is where I want to be.

This is the piece I was missing:  Although HubSpot apps get *installed* in your HubSpot account, they *run* somewhere else.  Now in my case, I only wanted to do one, singular thing: render some cards in my HubSpot dashboard.  But that was unusual.  Most of the time, HubSpot apps are more than just a support system for CRM cards.  Most HubSpot apps are integrations that run inside other pieces of software like Gmail, Wordpress, or Slack.  They authenticate themselves with OAuth so that they can then make API calls to HubSpot.

This was hard for me to see, because I only wanted to make API calls from HubSpot via the custom CRM cards.  And the truth is that technically, for my purposes, I shouldn't have needed an app.  I had no wish to pull HubSpot data into some other piece of software.  But although my needs were simple, I still had to follow the rules of HubSpot's app integrations.  That's the only way it works.  I needed to create an app, even if it didn't do anything.

Okay, we've created our app and configured its CRM cards.  Now it's time to connect the app to our HubSpot console.  However, according to their documentation, we learn that "Apps can’t be installed on developer accounts."  I suppose a developer account is fundamentally different from a regular HubSpot account, so in order to test the app, HubSpot recommends that you create a test account.  Let's do that.

Go back to your developer account's home page, click on "Manage test accounts" and click "Create test account."  Once the account is created, click the "Contacts" button in the top nav and then select "Contacts" (again) from the drop-down menu. Note that the test account comes with a couple of preprogrammed test contacts.

Now go back again to your developer account's home page and click on "Manage apps" (see fig. 2, above).  Click on the app you created above, and select "basic info" from the left-hand nav.  This should bring up a UI with two tabs: "App info" and "Auth."  Select the "Auth" tab. You should now be looking at the "Auth settings" (fig. 5).

Figure 5

At the top is the "App credentials" section, which contains fields for "App ID," "Client ID" and "Client Secret."  Those three values should all be filled in.  You'll want to keep track of the "Client ID" and "Client Secret" values, because we're going to use them in just a little bit.  Below those are fields labeled "Install URL - OAuth" (sometimes also referred to as the "Authorization URL") and "Redirect URL," which will be blank.

We must now go through the OAuth process as described.

That page breaks the OAuth process down into five steps, and ends with the note "Your app will not appear as a Connected App in a user's Integration Settings unless you complete the first two of these steps.  You must generate the refresh token and initial access token to have the app appear as 'connected.'"  In fact, we're going to complete the first three steps, but then we will stop.

Now let's deal with the "Install URL" and "Redirect URL" fields.  It looks like you should be able to type directly into the "Install URL" field, but you can't.  You populate this field by typing something into the "Redirect URL" and then saving the form.  And what should you type in there?  This should be a URL to a destination on your server - probably the same server that you will use to serve the API that will populate the CRM cards.  All the code at that URL needs to do is make a POST request.  For example, the one I made in Django looks like this:

    import requests
    from django.http import HttpResponse
    def hubspot_oauth(request):"", data = {
            "grant_type": "authorization_code",
            "client_id": "<client id from Hubspot dashboard>",
            "client_secret": "<client secret from Hubspot dashboard>",
            "redirect_uri": request.build_absolute_uri(),
            "code": request.GET.get("code")
        return HttpResponse()

HubSpot provides an example as well. 

Regarding these values: the "grant_type" needs to be the literal string "authorization_code." The values for "client_id" and "client_secret" come from the "Client ID" and "Client secret" values we noted above.  The value for "redirect_uri" is simply the redirect URL you just created, and should also match what is in your HubSpot app console.  I don't know why the request needs to send back its own URL, but it needs to be there.  Finally, the "code" is a value sent in the querystring of the incoming request, which will come from HubSpot, as we will see...

So code up something that will make a POST request like the one above, and paste the URL where that code is located into the "Redirect URL" field.  Now save the form.  After the save, the "Install URL" field will be populated.

There should be a button next to the "Install URL" field labeled "Copy full URL."  Click it.  Now paste the URL you just copied into your browser.  You should be presented with a list of accounts to connect the app to.  At a minimum this list will contain your developer account and your test account.  Select the test account by clicking on its name.  You may have to confirm that you want to connect your account to this "unverified" app.  When you click "confirm," a request will be sent from HubSpot to your redirect URL.  The request's querystring will contain the "code" parameter that is incorporated into the POST request sent back to HubSpot, and with that, the installation is complete.  If, as in the example above, your redirect URL returns nothing but an empty http response, you will find yourself looking at a blank page.  

But now go to the test account dashboard, navigate to the "Contacts" section, and select a contact.  You should see your custom card in the lower-right corner. You should also see a data fetch request coming into your API endpoint every time the page is loaded.  The request should include a querystring that contains a parameter "hs_object_id" and the HubSpot id of the contact in the page where the card was rendered.


HubSpot's custom CRM cards offer a really cool piece of functionality.  Unfortunately, HubSpot's documentation only seems to regard them as a small part of a larger integration running in some external code.  This can be confusing to developers unfamiliar with HubSpot, especially if their goal is only to make use of the cards without an integration.

NOTE:  The author wishes to thank Lance Erickson for his insights into the CRM cards and the HubSpot universe in general.  He works on the data team at "When I Work."

Share Twitter, LinkedIn, Facebook

Hey there...
Would you mind sharing this post? We would greatly appreciate it.
Thank you.