HubSpot to CDP Integration Guide
This guide describes how to push contact data from HubSpot to the CM.com Customer Data Platform (CDP) using a HubSpot Workflow with a Python Custom Code action. It is based on the provided Quick Setup document and expands it with best practices, validation, and troubleshooting.
Overview
You will build a HubSpot workflow that enrolls contacts on form submission, executes a Python 3.9 Custom Code action, and posts a structured payload to CM.com CDP’s Events API using your tenant, event, and product token.
Prerequisites
- HubSpot Operations Hub Professional or Enterprise (required for Custom Code actions).
- A HubSpot form that captures at least email, first name, last name, and mobile phone (you can extend fields as needed).
- Access to CM.com CDP with a valid tenant ID, event ID, and product token (for the Events API).
- Ability to map HubSpot contact/company properties to the payload schema used by CDP.
Architecture at a Glance
- Trigger: HubSpot Workflow (Form Submission).
- Transformation: Python Custom Code action formats HubSpot properties into CDP event payload.
- Transport: HTTPS POST to CM.com CDP Events API with product token authentication.
Step-by-Step Setup
1) Build the workflow and enroll from a form
- In HubSpot: Automation → Workflows → Create workflow from scratch.
- Add enrollment trigger: Form submission, then select the form you want to use.
- Optionally enable re-enrollment if repeat submissions should trigger the workflow again.
2) Add a Custom Code action (Python 3.9)
- Inside the workflow, click “+” and choose Custom code.
- Set Language = Python 3.9.
- Add the following Properties to include in code (names must match exactly):
- email → map to {{ contact.email }}
- firstname → {{ contact.firstname }}
- lastname → {{ contact.lastname }}
- company → {{ company.name }}
- mobilephone → {{ contact.mobilephone }}
- recordID → an internal ID (e.g., {{ company.hs_object_id }})
3) Paste and configure the Python code
Update placeholders for tenant, event, and product token before deploying.
import requests
import json
def main(event):
# --- 1. Extract input from the HubSpot workflow ---
email = event["inputFields"].get("email")
firstname = event["inputFields"].get("firstname")
lastname = event["inputFields"].get("lastname")
company = event["inputFields"].get("company")
mobilephone = event["inputFields"].get("mobilephone")
recordID = event["inputFields"].get("recordID")
# --- 2. Prepare the payload ---
payload = [{
"firstName": firstname,
"lastName": lastname,
"companyName": company,
"email": email,
"mobilePhoneNumber": mobilephone,
"phoneNumber": mobilephone, # duplicated intentionally if CDP expects either field
"recordIDCompany": recordID,
"event": "TM13", # replace with your event name/code if needed
"SMSOptIn": True
}]
# --- 3. Configure request details ---
# Replace placeholders with your CM values
tenant_id = "<YOUR_TENANT_ID>"
event_id = "<YOUR_EVENT_ID>"
product_token = "<YOUR_CM_PRODUCT_TOKEN>"
url = (
f"https://api.cdp.cm.com/events/v1.0/tenants/{tenant_id}/events/{event_id}"
"?Content-Type=application%2Fjson"
f"&X-CM-PRODUCTTOKEN={product_token}"
)
headers = {
"Content-Type": "application/json",
"X-CM-PRODUCTTOKEN": product_token
}
# --- 4. Send POST request ---
try:
response = requests.post(url, headers=headers, data=json.dumps(payload), timeout=10)
response.raise_for_status()
result = response.json() if response.content else {"status": response.status_code}
except Exception as e:
result = {"error": str(e)}
# --- 5. Return output for subsequent steps ---
return {
"outputFields": {
"email": email,
"cm_response": json.dumps(result)
}
}
Security note on secrets: If your token naming policy allows, use HubSpot Managed Secrets and read via environment variables. Some CM token names include dashes that may not be allowed as HubSpot secret names; adjust naming or store under an allowed alias if needed.
4) Test the action
- Use the Test button in the Custom Code action to run against a sample contact.
- Inspect logs and the cm_response output to verify a 2xx response and expected body.
- Once validated, turn the workflow on.
Payload Mapping Reference
| HubSpot Property (source) | Custom Code Input Name | CDP Payload Field |
|---|---|---|
| contact.email | ||
| contact.firstname | firstname | firstName |
| contact.lastname | lastname | lastName |
| company.name | company | companyName |
| contact.mobilephone | mobilephone | mobilePhoneNumber, phoneNumber |
| company.hs_object_id (example) | recordID | recordIDCompany |
Best Practices
- Validation: In code, .get() inputs and consider null-safe defaults (e.g., empty strings) to avoid KeyErrors.
- Timeouts and retries: Use a reasonable timeout (10–15s). If your volumes are high or network is flaky, add simple retry logic with backoff for 5xx responses.
- Data quality: Normalize phone numbers to E.164 and trim whitespace on string fields before sending.
- Observability: Return cm_response in outputFields to make the downstream debug easier; optionally log response.status_code and a short body snippet.
- Security: Prefer HubSpot Managed Secrets where feasible. If naming constraints conflict, store tokens under an allowed alias or rotate frequently.
Extending the Integration
- Add fields: Include more Properties to include in code (e.g., city, country, lifecycle stage) and map them in the payload.
- Conditional logic: Branch on consent flags or form choices to set event types or opt-in booleans.
- Upserts vs. events: Confirm with CM.com whether your event triggers identity resolution, and whether additional identifiers (e.g., external IDs) should be sent for better matching.
Troubleshooting
-
401/403: Verify X-CM-PRODUCTTOKEN is valid, active, and matches the tenant.
-
404: Check tenant_id and event_id; confirm the event exists in CDP.
-
415/400: Ensure Content-Type header and payload schema match CDP expectations; validate JSON structure.
-
Confirm the form is the enrollment trigger and the contact submitted that specific form.
-
Check re-enrollment if multiple submissions are expected to trigger repeatedly.
-
Ensure property names in HubSpot match your Custom Code input names exactly.
-
Validate mapping in the Payload Mapping Reference; align field casing and spelling (e.g., firstName vs firstname).
Operational Checklist
- Create the HubSpot workflow with Form Submission enrollment.
- Add Custom Code action and configure required input properties (email, firstname, lastname, company, mobilephone, recordID).
- Paste Python code and replace <YOUR_TENANT_ID>, <YOUR_EVENT_ID>, and <YOUR_CM_PRODUCT_TOKEN>.
- Test with a sample contact; verify a successful 2xx response and expected payload processing in CDP.
- Enable the workflow and monitor logs/outputs for the first week of operation.