Mastering HubSpot Connect: A Deep Dive into Custom Integration Development
HubSpot’s power lies in its extensibility. HubSpot Connect allows developers to build custom integrations that seamlessly interact with HubSpot’s core functionality. This article provides a comprehensive guide to developing robust and reliable HubSpot Connect integrations, focusing on best practices, authentication strategies, and real-world examples to enhance your HubSpot ecosystem. We will explore building custom integrations that leverage the HubSpot API for data synchronization, custom workflows, and enhanced reporting.
Understanding HubSpot Connect Architecture

- HubSpot API: This is the primary interface for interacting with HubSpot data. You’ll use API endpoints to read, write, and update information within HubSpot.
- Authentication: HubSpot employs OAuth 2.0 for authentication, ensuring secure access to user data. You’ll need to register your app with HubSpot and obtain API keys to authenticate requests.
- Webhooks: Webhooks allow HubSpot to notify your application of specific events in real-time. This is useful for triggering actions in your application based on HubSpot activity.
- Custom Objects: HubSpot allows you to define custom objects to store data that doesn’t fit into the standard HubSpot object types (Contacts, Companies, Deals, etc.). These objects can be fully integrated with the HubSpot platform, enhancing its data model.
Authentication with OAuth 2.0
HubSpot uses OAuth 2.0 for secure authentication of applications. The OAuth flow involves several steps:- Register your app: Create an app in your HubSpot developer account. This will give you a client ID and client secret.
- Authorization request: Redirect the user to HubSpot’s authorization URL with your client ID and a redirect URI.
- User authorization: The user logs in to HubSpot and grants your app access.
- Access token exchange: HubSpot redirects the user back to your redirect URI with an authorization code. Your app exchanges this code for an access token.
- API requests: Use the access token to make authenticated requests to the HubSpot API.
import requests
import urllib.parse
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
redirect_uri = "https://your-app.com/callback"
auth_url = "https://app.hubspot.com/oauth/authorize"
token_url = "https://api.hubapi.com/oauth/v1/token"
# Step 1: Redirect user to HubSpot authorization URL
params = {
"client_id": client_id,
"redirect_uri": redirect_uri,
"scope": "contacts", # Example scope: read access to contacts
"response_type": "code"
}
auth_redirect_url = auth_url + "?" + urllib.parse.urlencode(params)
print(f"Redirect user to: {auth_redirect_url}")
# In a real application, you'd redirect the user using a web framework.
# (After user authorizes, HubSpot redirects to your redirect_uri with a code)
# Step 2: Exchange authorization code for access token
auth_code = "THE_CODE_FROM_HUBSPOT_REDIRECT" # Replace with the actual code
token_data = {
"grant_type": "authorization_code",
"client_id": client_id,
"client_secret": client_secret,
"redirect_uri": redirect_uri,
"code": auth_code
}
token_response = requests.post(token_url, data=token_data)
token_response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
tokens = token_response.json()
access_token = tokens["access_token"]
refresh_token = tokens["refresh_token"]
expires_in = tokens["expires_in"]
print(f"Access Token: {access_token}")
print(f"Refresh Token: {refresh_token}")
# Now you can use the access_token to make API requests
Explanation:
- The code first defines the necessary variables: `client_id`, `client_secret`, and `redirect_uri`, which you obtain from your HubSpot app settings.
- It constructs the authorization URL and prints it to the console. In a real application, you would redirect the user to this URL.
- After the user authorizes the app, HubSpot redirects them back to your `redirect_uri` with an authorization code. This example simulates that by assigning the `auth_code` variable. You would need to retrieve this code from the request parameters.
- The code then exchanges the authorization code for an access token and a refresh token.
- Finally, it prints the access token and refresh token to the console. You can now use the access token to make authenticated API requests to HubSpot.
Handling Refresh Tokens
Access tokens have a limited lifespan. When an access token expires, you must use the refresh token to obtain a new access token. Here’s how to use the refresh token:import requests
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
refresh_token = "YOUR_REFRESH_TOKEN" # Store this securely!
token_url = "https://api.hubapi.com/oauth/v1/token"
refresh_data = {
"grant_type": "refresh_token",
"client_id": client_id,
"client_secret": client_secret,
"refresh_token": refresh_token
}
refresh_response = requests.post(token_url, data=refresh_data)
refresh_response.raise_for_status()
new_tokens = refresh_response.json()
new_access_token = new_tokens["access_token"]
new_refresh_token = new_tokens["refresh_token"] # Store the new refresh token!
new_expires_in = new_tokens["expires_in"]
print(f"New Access Token: {new_access_token}")
print(f"New Refresh Token: {new_refresh_token}")
Explanation:
- This code snippet uses the `refresh_token` (which you must have stored securely from the initial authorization) to request a new access token.
- The `grant_type` is set to `”refresh_token”`.
- The response contains a new access token, and potentially a new refresh token. Important: You must store the new refresh token, as the old one may no longer be valid.
Leveraging Webhooks for Real-Time Updates

Configuring Webhooks
You can configure webhooks through the HubSpot developer portal. When creating a webhook subscription, you need to specify:- Event type: The event that triggers the webhook (e.g., `contact.creation`, `deal.propertyChange`).
- Target URL: The URL of your application that will receive the webhook payload.
- Subscription type: Whether you want to subscribe to all events of a specific type, or only events that meet certain criteria.
- Properties included: A list of properties you wish to receive within the Webhook Payload.
- Event type: `contact.propertyChange`
- Target URL: `https://your-app.com/hubspot-webhook`
- Properties included: `lifecyclestage`
Handling Webhook Payloads
Your application needs to be able to receive and process webhook payloads. The payload is a JSON object that contains information about the event that triggered the webhook. Here’s a Python example using Flask to handle webhook requests:from flask import Flask, request, jsonify
import json
app = Flask(__name__)
@app.route('/hubspot-webhook', methods=['POST'])
def hubspot_webhook():
if request.method == 'POST':
data = request.get_json()
# Verify the signature (Important for security!)
# You should implement a secure verification method. This is a placeholder.
if not verify_signature(request):
return jsonify({'status': 'error', 'message': 'Invalid signature'}), 401
print("Received webhook payload:")
print(json.dumps(data, indent=2))
# Process the webhook data here
# Example:
for event in data:
if event['propertyName'] == 'lifecyclestage':
contact_id = event['objectId']
new_lifecycle_stage = event['propertyValue']
print(f"Contact {contact_id} lifecycle stage updated to {new_lifecycle_stage}")
# Perform actions based on the updated lifecycle stage, e.g., update a record in your external system
return jsonify({'status': 'success'}), 200
else:
return jsonify({'status': 'error', 'message': 'Method not allowed'}), 405
def verify_signature(request):
# This is a PLACEHOLDER. HubSpot provides mechanisms to verify
# the authenticity of webhooks. Implement one of these in a
# production environment. See the HubSpot documentation.
# Common methods involve a secret key and hashing.
# Returning True here disables signature verification, making it
# vulnerable to spoofing.
return True
if __name__ == '__main__':
app.run(debug=True, port=5000)
Explanation:
- This code defines a Flask route `/hubspot-webhook` that listens for POST requests.
- It extracts the JSON payload from the request using `request.get_json()`.
- Critical Security Note: The code includes a placeholder for signature verification. You must implement a proper signature verification mechanism provided by HubSpot to ensure that the webhook requests are actually coming from HubSpot and haven’t been tampered with. Failure to do so can expose your application to security vulnerabilities. Consult the HubSpot developer documentation for detailed instructions on webhook signature verification.
- The code iterates through the events in the payload and processes the data. In this example, it checks if the `propertyName` is `lifecyclestage` and prints the contact ID and new lifecycle stage to the console. You would replace this with your desired logic, such as updating a record in your external system.
- The function returns a JSON response with a status code of 200 to acknowledge receipt of the webhook.
Creating and Managing Custom Objects
Custom objects extend HubSpot’s data model, allowing you to store and manage data that is specific to your business. This is particularly useful for integrations that deal with data that doesn’t naturally fit into HubSpot’s standard objects (Contacts, Companies, Deals, Tickets).Defining Custom Object Properties
Before you can use a custom object, you need to define its properties. Properties define the data that can be stored for each instance of the object. You can define various property types, including text, number, date, and boolean. You can define custom objects and their properties via the HubSpot API. The following Python code demonstrates this using the `requests` library:import requests
import json
access_token = "YOUR_HUBSPOT_ACCESS_TOKEN" # Replace with your access token
api_url = "https://api.hubapi.com/crm/v3/schemas"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
# Define the schema for the custom object (e.g., "Event")
data = {
"name": "event", # Lowercase, singular
"displayName": "Event",
"pluralDisplayName": "Events",
"objectTypeId": "pvt.hubspot_defined", # Required field
"properties": [
{
"name": "event_name",
"label": "Event Name",
"type": "string",
"fieldType": "text",
"groupName": "eventinformation"
},
{
"name": "event_date",
"label": "Event Date",
"type": "datetime",
"fieldType": "date",
"groupName": "eventinformation"
},
{
"name": "event_location",
"label": "Event Location",
"type": "string",
"fieldType": "text",
"groupName": "eventinformation"
}
],
"groups": [
{
"name": "eventinformation",
"label": "Event Information",
"displayOrder": 1
}
],
"primaryDisplayProperty": "event_name",
"requiredProperties": ["event_name", "event_date"]
}
response = requests.post(api_url, headers=headers, data=json.dumps(data))
response.raise_for_status()
if response.status_code == 201:
print("Custom object created successfully!")
print(json.dumps(response.json(), indent=2))
else:
print(f"Error creating custom object: {response.status_code} - {response.text}")
Explanation:
- This code sends a POST request to the HubSpot API endpoint for creating custom object schemas.
- The `data` dictionary defines the schema for the custom object. This includes the object’s name, display names, properties, and groups.
- The `properties` list defines the properties of the custom object, such as `event_name`, `event_date`, and `event_location`.
- The `groups` list organizes the properties into groups for display in the HubSpot interface.
- The `primaryDisplayProperty` specifies which property should be used as the primary display name for the object.
- The `requiredProperties` list specifies which properties are required when creating a new instance of the object.
- The `name` of the custom object must be lowercase and singular (e.g., “event”).
- The `objectTypeId` must be set to `”pvt.hubspot_defined”` when creating a new custom object.
- Always handle potential errors and log them appropriately.
Associating Custom Objects with Standard Objects
One of the most powerful features of custom objects is the ability to associate them with standard HubSpot objects like Contacts, Companies, and Deals. This allows you to create a rich data model that reflects the relationships between your custom data and your existing HubSpot data. To associate custom objects, you need to define associations in the custom object’s schema using the Associations API. For example, to associate `event` objects with `contact` objects, you’d add an `associations` section to your schema definition.import requests
import json
access_token = "YOUR_HUBSPOT_ACCESS_TOKEN"
api_url = "https://api.hubapi.com/crm/v3/schemas/event" # Assuming 'event' object already created
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
data = {
"associations": [
{
"toObjectTypeId": "0-1", # 0-1 is the objectTypeId for Contacts
"associationTypes": [
{
"category": "HUBSPOT_DEFINED",
"typeId": 4 # A common "Contact to Event" association. Check HubSpot docs.
}
]
}
]
}
response = requests.patch(api_url, headers=headers, data=json.dumps(data))
response.raise_for_status()
if response.status_code == 200:
print("Custom object association created successfully!")
print(json.dumps(response.json(), indent=2))
else:
print(f"Error creating custom object association: {response.status_code} - {response.text}")
Explanation:
- This code sends a PATCH request to update the schema of the `event` custom object (assuming it already exists).
- The `associations` section defines an association with the `contact` object (objectTypeId `0-1`).
- The `associationTypes` array specifies the types of associations that are allowed between the two objects. The `typeId` represents a specific, predefined association type (e.g., “Contact attended Event”). You should consult the HubSpot documentation for appropriate `typeId` values for different object pairings.
Building Custom Modules and Actions
While HubSpot’s built-in features cover a wide range of use cases, sometimes you need to create custom modules and actions to extend HubSpot’s functionality even further. This section covers creating these within the CMS and associating them with your Connect integrations.Creating Custom Modules
Custom modules are reusable components that can be added to HubSpot pages and emails. They allow you to create custom layouts, display dynamic content, and integrate with external systems. They are built using HTML, CSS, and HubL (HubSpot’s templating language). Here’s a simplified example of a custom module that displays a contact’s name and email address:<!-- module.html -->
<div class="contact-info">
<h2>Contact Information</h2>
<p>Name: {{ contact.firstname }} {{ contact.lastname }}</p>
<p>Email: {{ contact.email }}</p>
</div>
<!-- module.css -->
.contact-info {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
h2 {
font-size: 1.2em;
margin-bottom: 5px;
}
Explanation:
- `module.html` defines the HTML structure of the module. It uses HubL to access the contact’s `firstname`, `lastname`, and `email` properties. The `{{ … }}` syntax is used to output the values of these properties.
- `module.css` defines the styling for the module.
- Navigate to the Design Manager in HubSpot (Marketing > Files and Templates > Design Tools).
- Create a new module file.
- Add the HTML, CSS, and JavaScript code for your module.
- Define the module’s fields using the module editor. These fields allow users to customize the module’s content and behavior.
- Publish the module.
Creating Custom Actions (Workflows)
Custom actions extend HubSpot’s workflow functionality. They allow you to execute custom code within a workflow, enabling you to integrate with external systems, perform complex data transformations, or trigger other HubSpot actions. Custom Actions are often part of a larger Connect integration. To create a custom action, you need to:- Have a HubSpot Developer Account
- Define a Function: This is the function code that will be executed by the workflow action. It usually interacts with an external API and uses access tokens or API keys.
- Create a Custom Action definition. This will include the name, description, and inputs for the custom action.
# Example: (Simplified) External Function (e.g., in AWS Lambda, Google Cloud Functions)
# triggered by HubSpot Custom Action
def my_custom_action(contact_email, discount_code):
"""
Applies a discount to a contact in an external system.
Args:
contact_email (str): The contact's email address.
discount_code (str): The discount code to apply.
Returns:
dict: A dictionary containing the result of the action.
"""
# --- REPLACE WITH YOUR ACTUAL INTEGRATION LOGIC ---
# Here, you'd make API calls to your external system to apply the discount
# using the contact_email and discount_code.
# You'd need to handle authentication and error handling appropriately.
# The code below is just a placeholder.
print(f"Simulating applying discount {discount_code} to {contact_email}")
success = True # Placeholder - would come from the external system's API response
# --- END REPLACE ---
if success:
return {"result": "Discount applied successfully."}
else:
return {"error": "Failed to apply discount."}
Explanation:
- This is the external function code that is triggered by HubSpot
- It receives the email address and discount code.
- It would then call the integration logic to apply the discount.
Feature | Custom Modules | Custom Actions |
---|---|---|
Purpose | Create reusable content blocks for pages and emails | Execute custom code within workflows |
Location | Design Manager | Workflows |
Language | HTML, CSS, HubL | External system’s code (e.g., Python, Node.js) |
Use Cases | Displaying dynamic content, integrating with external systems | Data transformations, triggering external events |