HubSpot Native Workflows: Geographic Routing & Lead Stage Automation
This playbook details the technical configuration, step-by-step UI setups, and custom-coded Node.js workflow actions required to orchestrate geographic custom object routing and lead generation natively inside HubSpot.
🗺️ 1. Geographic Zip-to-Franchise Association Workflow
Because The Dog Wizard maps incoming clients (Contacts) to franchise locations (Companies) using custom objects (Zip Codes and Territories), we utilize a HubSpot Custom Code Action (requires Operations Hub Pro) inside a Contact-based workflow to perform high-speed database traversals and auto-association.
⚙️ UI Setup Specifications
- Workflow Type: Contact-based (re-enrollment enabled on Postal Code change).
- Enrollment Trigger: Contact property
Postal Code/Zip Codeis known. - Action 1: Add a “Custom Code” action.
- Language: Node.js 18.x
- Secrets: Add
HUBSPOT_ACCESS_TOKENas a workflow secret. - Property Inputs: Select
Postal codeand map it to input variablezipCode.
- Action 2: Once associated, send a Slack or internal notification to the Front Office team if the resolved company has
Managed by Front Office=True.
💻 Custom Code Script: Zip Code Traversal
Copy and paste this script directly into the HubSpot Custom Code block:
const axios = require('axios');
exports.main = async (event, callback) => {
const zipCode = event.inputFields['zipCode'];
const contactId = event.object.objectId;
if (!zipCode) {
console.log("No zip code found on enrolling contact record.");
return callback();
}
// Replace these with your actual HubSpot Custom Object Type IDs
const ZIP_CODE_OBJECT_TYPE_ID = "2-10543932"; // Example: Replace with your actual schema ID
const TERRITORY_OBJECT_TYPE_ID = "2-10543933"; // Example: Replace with your actual schema ID
const token = process.env.HUBSPOT_ACCESS_TOKEN;
const client = axios.create({
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
try {
console.log(`Step 1: Searching for Custom Zip Code Object record matching '${zipCode}'...`);
const zipSearchRes = await client.post(`https://api.hubapi.com/crm/v3/objects/${ZIP_CODE_OBJECT_TYPE_ID}/search`, {
filterGroups: [{
filters: [{
propertyName: 'zip_code',
operator: 'EQ',
value: zipCode
}]
}],
limit: 1
});
if (!zipSearchRes.data.results || zipSearchRes.data.results.length === 0) {
console.log(`❌ No Zip Code custom object found matching '${zipCode}'. Ending workflow.`);
return callback();
}
const matchedZipRecordId = zipSearchRes.data.results[0].id;
console.log(`✅ Found custom Zip Code record ID: ${matchedZipRecordId}`);
console.log(`Step 2: Retrieving associated Territory record...`);
const territoryAssocRes = await client.get(`https://api.hubapi.com/crm/v4/objects/${ZIP_CODE_OBJECT_TYPE_ID}/${matchedZipRecordId}/associations/${TERRITORY_OBJECT_TYPE_ID}`);
if (!territoryAssocRes.data.results || territoryAssocRes.data.results.length === 0) {
console.log("❌ Zip Code record has no associated Territory custom object. Ending workflow.");
return callback();
}
const matchedTerritoryId = territoryAssocRes.data.results[0].toObjectId;
console.log(`✅ Found custom Territory record ID: ${matchedTerritoryId}`);
console.log(`Step 3: Retrieving associated Franchise Company record...`);
const companyAssocRes = await client.get(`https://api.hubapi.com/crm/v4/objects/${TERRITORY_OBJECT_TYPE_ID}/${matchedTerritoryId}/associations/companies`);
if (!companyAssocRes.data.results || companyAssocRes.data.results.length === 0) {
console.log("❌ Territory record has no associated Company. Ending workflow.");
return callback();
}
const resolvedCompanyId = companyAssocRes.data.results[0].toObjectId;
console.log(`✅ Resolved Franchise Company record ID: ${resolvedCompanyId}`);
console.log(`Step 4: Creating Contact-to-Company association...`);
await client.put(`https://api.hubapi.com/crm/v4/objects/contacts/${contactId}/associations/companies/${resolvedCompanyId}`, [
{
associationCategory: "HUBSPOT_DEFINED",
associationTypeId: 1 // Standard Contact to Company Link
}
]);
console.log(`🎉 Success! Contact ${contactId} has been successfully associated with Company ${resolvedCompanyId} geographically.`);
callback({
outputFields: {
associatedCompanyId: resolvedCompanyId
}
});
} catch (err) {
console.error("❌ Error running custom zip-code routing action:", err.message);
if (err.response) console.error("HubSpot Response Error:", err.response.data);
callback(err);
}
};🧭 2. Lead Object Generation (Sales Hub Pro)
This Contact-based workflow automatically provisions standard Sales Hub Pro Lead Records as soon as a high-priority contact lands from PocketSuite.
⚙️ UI Setup Specifications
- Workflow Type: Contact-based.
- Enrollment Trigger: Contact property
pocket_suite_client_idis known. - Action 1: “Create Lead” (native HubSpot Sales Hub Pro action).
- Pipeline: Sales Pipeline / Prospecting Workspace.
- Initial Stage:
New. - Name:
Lead - [Contact First Name] [Contact Last Name]. - Owner: Rotated among Front Office team members.
- Associations: Check both “Associate with Contact” and “Associate with Company” (this links the Lead to the geographic franchise Company resolved in the step above).
🔄 3. Lead Stage Automation (Activity-Driven)
This workflow keeps the Leads pipeline up-to-date programmatically based on the actions logged onto the Contact timeline by our Quo (OpenPhone) SMS/call system.
⚙️ UI Setup Specifications
Create a single Contact-based workflow with branching logic:
graph TD Start[Contact Activity Change] --> CheckActivity{Which Activity Logged?} CheckActivity -->|SMS Logged OR ps_outreach_sent = true| Attempting[Set Lead Stage = "Attempting"] CheckActivity -->|Call Logged OR ps_connection_established = true| Connected[Set Lead Stage = "Connected"] CheckActivity -->|Evaluation Booking Registered| Qualified[Set Lead Stage = "Qualified"]
- Enrollment Trigger:
- Activity:
SMS messageis created on Contact timeline ORps_outreach_sent=True. - OR Activity:
Callis logged on Contact timeline ORps_connection_established=True. - OR Contact Property
pocket_suite_lead_sourceis updated to a booking status.
- Activity:
- Branching Logic:
- Branch A: Attempting Stage
- Trigger: SMS activity is logged OR
ps_outreach_sentis true. - Action: Use “Update Lead Stage” native action ➡️ Set to
Attempting.
- Trigger: SMS activity is logged OR
- Branch B: Connected Stage
- Trigger: Call activity is logged OR
ps_connection_establishedis true. - Action: Use “Update Lead Stage” native action ➡️ Set to
Connected.
- Trigger: Call activity is logged OR
- Branch C: Qualified Stage
- Trigger: Evaluation booked in PocketSuite (mapped to the Contact record).
- Action: Use “Update Lead Stage” native action ➡️ Set to
Qualified.
- Branch A: Attempting Stage
🏆 Architectural Gains
By using native HubSpot Workflows coupled with custom-coded Node.js traversal actions, we keep data pipelines fully automated, real-time, and zero-cost, preventing external middleware breakage.