In a nutshell: This guide walks you through connecting Rodz to HubSpot CRM so that every intent signal (funding round, leadership change, hiring spike, product launch) flows automatically into your CRM as a timeline event, property update, or workflow trigger. You will set up a Rodz webhook, route payloads through middleware, map signal data to HubSpot properties, and build workflows that assign tasks the moment a signal fires. Total setup time: roughly 45 minutes if you already have API access on both sides.
What Does “Connecting Rodz to HubSpot” Actually Mean?
Rodz detects intent signals in real time. A target account raises a Series B. A VP of Sales leaves. A competitor wins a public tender. These events carry timing information that sales teams need to act on quickly. The problem is that most teams discover these signals in one tool and then manually copy-paste them into HubSpot, where their pipeline actually lives. That gap between detection and action is where deals slip through.
Connecting Rodz to HubSpot eliminates that gap. When a signal fires, the data lands directly inside HubSpot on the relevant company or contact record. No manual entry, no context switching, no delay. Reps open their CRM in the morning and the overnight signals are already there, attached to the right records, with tasks and notifications ready.
The integration works through three layers:
- Rodz webhooks push signal events the moment they are detected.
- Middleware (either a custom server or an automation platform like Make) receives, transforms, and routes the data.
- HubSpot API calls create or update records, log timeline events, and trigger workflows.
If you have not set up Rodz webhooks yet, start with the webhook setup guide before continuing. And if you need a refresher on available endpoints and rate limits, the Rodz API reference covers everything.
Prerequisites
Before you begin, make sure you have the following ready:
- A Rodz account with API access and at least one active signal configured. API access is available on all paid Rodz plans.
- A Rodz API key generated from your dashboard under Settings > API Keys. If you have not done this yet, the authentication guide will walk you through it.
- A HubSpot account (Professional or Enterprise tier recommended). You need access to custom properties, workflows, and the HubSpot API. Free and Starter plans lack some of the automation features described here.
- A HubSpot private app with the following scopes:
crm.objects.companies.write,crm.objects.contacts.write,crm.objects.deals.write,timelineandautomation. Create one under Settings > Integrations > Private Apps in your HubSpot portal. - A middleware layer. This guide covers two approaches: a custom Node.js server, and Make (a no-code automation platform). Pick whichever matches your technical comfort level.
- cURL or Postman installed for testing API calls during setup.
- Basic familiarity with JSON and REST APIs.
Architecture Overview
Understanding how the pieces fit together will make the step-by-step instructions much easier to follow. Here is the data flow:
Rodz Signal Fires
|
v
Rodz Webhook POST ──> Your Middleware (Make scenario or Node.js server)
|
v
Transform payload
Match to HubSpot company/contact
Map Rodz fields to HubSpot properties
|
v
HubSpot API calls:
- Update company properties
- Create timeline event
- Trigger workflow (optional)
The middleware layer is critical. Rodz and HubSpot use different data structures, different field names, and different identifier systems. The middleware handles the translation. It receives the Rodz webhook payload, looks up the corresponding company in HubSpot (using domain or company name), maps the signal data to HubSpot custom properties, and pushes the result through the HubSpot API.
You can also skip the middleware entirely and use HubSpot Operations Hub if you have an Enterprise license, but that approach limits your ability to transform data before it enters HubSpot. For most teams, a middleware layer gives more flexibility.
Step 1: Create Custom Properties in HubSpot
Before any signal data can land in HubSpot, you need properties to receive it. HubSpot does not have native fields for intent signals, so you will create custom ones.
Go to Settings > Properties > Company Properties in your HubSpot portal and create the following:
| Property Name | Internal Name | Type | Description |
|---|---|---|---|
| Last Rodz Signal | rodz_last_signal | Single-line text | The most recent signal type detected |
| Last Signal Date | rodz_last_signal_date | Date picker | When the most recent signal fired |
| Signal Details | rodz_signal_details | Multi-line text | JSON or human-readable summary of the signal |
| Rodz Signal Count | rodz_signal_count | Number | Total number of signals received for this company |
| Rodz Company ID | rodz_company_id | Single-line text | The unique company identifier from Rodz |
| Signal Category | rodz_signal_category | Dropdown | Values: Funding, Hiring, Leadership Change, Expansion, Product Launch, Partnership, Acquisition, Competitive |
You can create these properties manually through the HubSpot UI, or use the HubSpot API to create them programmatically. Here is an example cURL call for creating the rodz_last_signal property:
curl -X POST https://api.hubapi.com/crm/v3/properties/companies \
-H "Authorization: Bearer YOUR_HUBSPOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "rodz_last_signal",
"label": "Last Rodz Signal",
"type": "string",
"fieldType": "text",
"groupName": "companyinformation",
"description": "Most recent intent signal detected by Rodz"
}'
Repeat this pattern for each property, adjusting the name, label, type, and fieldType values. For the dropdown property (rodz_signal_category), include an options array in the request body with your signal categories.
A tip: group these properties under a custom property group called “Rodz Signals” so they are easy to find on company records. Create the group first:
curl -X POST https://api.hubapi.com/crm/v3/properties/companies/groups \
-H "Authorization: Bearer YOUR_HUBSPOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "rodz_signals",
"label": "Rodz Signals"
}'
Then set "groupName": "rodz_signals" when creating each property.
Step 2: Set Up the Rodz Webhook
If you have already configured a webhook following the Rodz webhook guide, you can reuse it. Otherwise, register a new one pointing to your middleware endpoint:
curl -X POST https://api.rodz.io/v1/webhooks \
-H "Authorization: Bearer YOUR_RODZ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourdomain.com/webhooks/rodz-to-hubspot",
"events": ["signal.fired"],
"description": "HubSpot CRM sync"
}'
Save the signing_secret from the response. You will use it to verify that incoming payloads genuinely come from Rodz.
The webhook payload for a signal.fired event looks like this:
{
"event": "signal.fired",
"timestamp": "2026-03-10T09:15:22Z",
"signal": {
"id": "sig_7f3a9b2c",
"type": "funding",
"name": "Series B Funding Round",
"fired_at": "2026-03-10T09:15:00Z"
},
"company": {
"id": "comp_4e8d1a6f",
"name": "Acme Corp",
"domain": "acmecorp.com",
"industry": "SaaS",
"employee_count": 250,
"country": "France"
},
"details": {
"amount": 15000000,
"currency": "EUR",
"investors": ["VC Fund Alpha", "Growth Partners"],
"source_url": "https://example.com/article/acme-series-b"
}
}
The company.domain field is what you will use to match against HubSpot company records. Domain-based matching is the most reliable approach because HubSpot already deduplicates companies by domain. If your CRM contains existing duplicates that could cause matching issues, consider running a deduplication tool like Dedupe.ly before setting up the integration.
Step 3: Build the Middleware
This is where you choose your path. Both approaches produce the same result. The custom server gives you full control. Make gives you speed and a visual interface.
Option A: Custom Node.js Middleware
Here is a complete middleware server that receives Rodz webhooks, matches companies in HubSpot, and updates their properties:
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');
const app = express();
app.use(express.json());
const RODZ_SIGNING_SECRET = process.env.RODZ_SIGNING_SECRET;
const HUBSPOT_TOKEN = process.env.HUBSPOT_TOKEN;
// Verify Rodz webhook signature
function verifySignature(payload, signature) {
const expected = crypto.createHmac('sha256', RODZ_SIGNING_SECRET).update(JSON.stringify(payload)).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Find a company in HubSpot by domain
async function findHubSpotCompany(domain) {
const response = await axios.post(
'https://api.hubapi.com/crm/v3/objects/companies/search',
{
filterGroups: [
{
filters: [
{
propertyName: 'domain',
operator: 'EQ',
value: domain,
},
],
},
],
properties: ['name', 'domain', 'rodz_signal_count'],
},
{
headers: { Authorization: `Bearer ${HUBSPOT_TOKEN}` },
},
);
return response.data.total > 0 ? response.data.results[0] : null;
}
// Update company properties in HubSpot
async function updateCompanySignal(companyId, signalData) {
const currentCount = signalData.currentCount || 0;
await axios.patch(
`https://api.hubapi.com/crm/v3/objects/companies/${companyId}`,
{
properties: {
rodz_last_signal: signalData.signal.type,
rodz_last_signal_date: signalData.signal.fired_at,
rodz_signal_details: JSON.stringify(signalData.details),
rodz_signal_count: String(currentCount + 1),
rodz_company_id: signalData.company.id,
rodz_signal_category: mapSignalCategory(signalData.signal.type),
},
},
{
headers: { Authorization: `Bearer ${HUBSPOT_TOKEN}` },
},
);
}
// Map Rodz signal types to HubSpot dropdown values
function mapSignalCategory(type) {
const mapping = {
funding: 'Funding',
hiring: 'Hiring',
leadership_change: 'Leadership Change',
expansion: 'Expansion',
product_launch: 'Product Launch',
partnership: 'Partnership',
acquisition: 'Acquisition',
competitor_move: 'Competitive',
};
return mapping[type] || 'Other';
}
// Webhook endpoint
app.post('/webhooks/rodz-to-hubspot', async (req, res) => {
const signature = req.headers['x-rodz-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously
try {
const { signal, company, details } = req.body;
const hubspotCompany = await findHubSpotCompany(company.domain);
if (hubspotCompany) {
const currentCount = parseInt(hubspotCompany.properties.rodz_signal_count || '0', 10);
await updateCompanySignal(hubspotCompany.id, {
signal,
company,
details,
currentCount,
});
console.log(`Updated ${company.name} with signal: ${signal.type}`);
} else {
console.log(`No HubSpot match for domain: ${company.domain}`);
// Optionally create a new company record here
}
} catch (err) {
console.error('Processing error:', err.message);
}
});
app.listen(3000, () => console.log('Middleware running on port 3000'));
Deploy this on any server or cloud function that can receive HTTPS traffic. Make sure to set the RODZ_SIGNING_SECRET and HUBSPOT_TOKEN environment variables.
Option B: Make (No-Code Middleware)
If you prefer a visual, no-code approach, Make is an excellent choice. Here is how to set it up:
- Create a new scenario in your Make dashboard.
- Add a Webhook trigger module. Select “Custom webhook” and copy the generated URL. This is the URL you will register with Rodz (Step 2 above).
- Add a JSON Parse module to extract the payload fields. Map the incoming data to individual variables:
signal.type,company.domain,company.name,details, etc. - Add an HTTP module to search HubSpot for the company. Configure it as a POST request to
https://api.hubapi.com/crm/v3/objects/companies/searchwith the domain filter shown in the Node.js example above. Include your HubSpot token in the Authorization header. - Add a Router to handle two paths: one for when a company is found, one for when it is not.
- On the “company found” path, add an HTTP module to PATCH the company with the updated signal properties. Map each Rodz field to the corresponding HubSpot property.
- On the “company not found” path, either log the miss for manual review or add an HTTP module to create a new company in HubSpot.
- Turn on the scenario and set it to run on every incoming webhook (instant trigger).
The Make scenario typically takes 15-20 minutes to build, and you can test each module individually before activating the full flow.
Step 4: Map Signal Data to HubSpot Properties
The property mapping is the most important part of the integration. Get it right and your reps will have clean, actionable data. Get it wrong and they will ignore the fields entirely.
Here is the complete mapping between Rodz webhook payload fields and HubSpot company properties:
| Rodz Payload Field | HubSpot Property | Transformation |
|---|---|---|
signal.type | rodz_last_signal | Direct copy (string) |
signal.fired_at | rodz_last_signal_date | Convert to midnight UTC date format |
details (full object) | rodz_signal_details | Stringify to JSON, or build a human-readable summary |
| Increment counter | rodz_signal_count | Read current value, add 1, write back |
company.id | rodz_company_id | Direct copy (string) |
signal.type | rodz_signal_category | Map through the category function (see code above) |
A few mapping considerations worth noting:
Date format. HubSpot date properties expect a Unix timestamp in milliseconds at midnight UTC. If Rodz sends 2026-03-10T09:15:00Z, you need to strip the time portion and convert 2026-03-10T00:00:00Z to 1772956800000. In JavaScript:
const date = new Date(signal.fired_at);
date.setUTCHours(0, 0, 0, 0);
const hubspotDate = date.getTime();
Signal details. Storing the raw JSON in a multi-line text field is convenient for debugging, but reps will not read JSON. Consider building a human-readable string instead:
function formatDetails(signal, details) {
switch (signal.type) {
case 'funding':
return `${signal.name}: ${details.currency} ${(details.amount / 1000000).toFixed(1)}M raised from ${details.investors.join(', ')}`;
case 'hiring':
return `${details.job_count} new positions posted, including ${details.key_roles.join(', ')}`;
case 'leadership_change':
return `${details.person_name} appointed as ${details.new_role}`;
default:
return JSON.stringify(details);
}
}
This produces entries like “Series B Funding Round: EUR 15.0M raised from VC Fund Alpha, Growth Partners” which a sales rep can scan in two seconds.
Signal count. This is a read-modify-write operation. In a high-volume environment, you could face race conditions if two signals fire for the same company simultaneously. For most Rodz users this is unlikely, but if you want to be safe, use HubSpot’s calculation properties or handle the increment server-side with a lock.
Step 5: Create HubSpot Timeline Events (Optional but Recommended)
Updating properties gives you the current state. Timeline events give you the history. When a rep opens a company record, they can scroll through the timeline and see every signal that ever fired, in chronological order.
To create timeline events, you first need to define a custom timeline event template in HubSpot. This is done through the Timeline Events API:
curl -X POST https://api.hubapi.com/crm/v3/timeline/event-templates \
-H "Authorization: Bearer YOUR_HUBSPOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Rodz Intent Signal",
"objectType": "COMPANY",
"headerTemplate": "{{signal_type}}: {{signal_name}}",
"detailTemplate": "Signal fired on {{fired_at}}. Details: {{signal_details}}",
"tokens": [
{ "name": "signal_type", "type": "string", "label": "Signal Type" },
{ "name": "signal_name", "type": "string", "label": "Signal Name" },
{ "name": "fired_at", "type": "date", "label": "Fired At" },
{ "name": "signal_details", "type": "string", "label": "Details" }
]
}'
Save the eventTemplateId from the response. Then, in your middleware, add a call to create a timeline event each time a signal is processed:
async function createTimelineEvent(companyId, signal, details) {
await axios.post(
'https://api.hubapi.com/crm/v3/timeline/events',
{
eventTemplateId: 'YOUR_EVENT_TEMPLATE_ID',
objectId: companyId,
timestamp: new Date(signal.fired_at).getTime(),
tokens: {
signal_type: mapSignalCategory(signal.type),
signal_name: signal.name,
fired_at: signal.fired_at,
signal_details: formatDetails(signal, details),
},
},
{
headers: { Authorization: `Bearer ${HUBSPOT_TOKEN}` },
},
);
}
Timeline events are especially valuable for accounts with long sales cycles. A rep preparing for a call six months into the pipeline can look back and see every signal that has touched the account, giving them fresh conversation starters.
Step 6: Trigger HubSpot Workflows from Signals
With signal data flowing into HubSpot properties, you can now build workflows that react to those signals automatically. Here are four workflow patterns that Rodz users commonly deploy:
Workflow 1: Assign a Task on Funding Signal
- Trigger: Company property
rodz_signal_categoryis updated to “Funding” - Action 1: Create a task for the company owner: “Review funding signal and call within 24h”
- Action 2: Set task due date to tomorrow
- Action 3: Send an internal notification to the assigned rep via email or Slack
Workflow 2: Move Deal Stage on Hiring Spike
- Trigger: Company property
rodz_last_signalis updated to “hiring” ANDrodz_signal_detailscontains “VP” or “Director” - Action: If a deal exists for this company in the “Prospecting” stage, move it to “Qualified Lead”
Workflow 3: Alert Team on Competitive Signal
- Trigger: Company property
rodz_signal_categoryis updated to “Competitive” - Action 1: Send a Slack notification to the #competitive-intel channel
- Action 2: Create a note on the company record with the signal details
- Action 3: If a deal exists, add an activity note to the deal as well
Workflow 4: Re-Engage Closed-Lost Deals
- Trigger: Company property
rodz_signal_categoryis updated (any value) AND the company has a deal in “Closed Lost” from the last 12 months - Action: Create a task for the original deal owner: “Closed-lost account has a new signal. Reassess the opportunity.”
To create these workflows, go to Automation > Workflows in HubSpot, select “Company-based” as the workflow type, and set the enrollment trigger to a property change on one of your Rodz custom properties.
These workflows eliminate the question “what do I do with this signal?” The system tells the rep exactly what to do, and when.
Testing the Full Pipeline
Before you rely on this integration for your live pipeline, test it end-to-end. Here is a systematic approach:
Test 1: Send a Simulated Webhook
Use cURL to send a test payload to your middleware endpoint. Construct a realistic signal.fired event with a company domain that exists in your HubSpot CRM:
curl -X POST https://yourdomain.com/webhooks/rodz-to-hubspot \
-H "Content-Type: application/json" \
-H "x-rodz-signature: YOUR_TEST_SIGNATURE" \
-d '{
"event": "signal.fired",
"timestamp": "2026-03-10T10:00:00Z",
"signal": {
"id": "sig_test_001",
"type": "funding",
"name": "Series A Funding Round",
"fired_at": "2026-03-10T10:00:00Z"
},
"company": {
"id": "comp_test_001",
"name": "Test Company",
"domain": "testcompany.com",
"industry": "Technology",
"employee_count": 50,
"country": "France"
},
"details": {
"amount": 5000000,
"currency": "EUR",
"investors": ["Seed Fund"],
"source_url": "https://example.com/test"
}
}'
For testing purposes, you can skip signature verification or generate a valid signature using your signing secret.
Test 2: Verify HubSpot Updates
After sending the test payload, open the corresponding company record in HubSpot. Check that:
- The
rodz_last_signalproperty shows “funding” - The
rodz_last_signal_dateshows the correct date - The
rodz_signal_detailsfield contains a readable summary - The
rodz_signal_countincremented by one - If you implemented timeline events, the event appears on the company timeline
Test 3: Trigger a Workflow
If you created any of the workflows from Step 6, verify that they fire. Check that tasks are created, notifications are sent, and deal stages update as expected.
Test 4: Edge Cases
Test what happens when:
- The company domain does not exist in HubSpot (your middleware should handle this gracefully)
- The same signal fires twice in quick succession (idempotency check)
- The HubSpot API rate limit is hit (your middleware should implement retry with backoff)
- The webhook payload contains unexpected fields (your parser should not break)
Frequently Asked Questions
Do I need a paid HubSpot plan for this integration?
You need at least HubSpot Professional to access workflows and custom timeline events. If you only want to update custom properties without automation, the Starter plan works, but you lose the ability to trigger actions automatically. The free plan does support custom properties and API access, so a basic version of this integration (properties only, no workflows) can work there.
Can I sync signals to contacts instead of companies?
Yes. The same approach works for contact records. Instead of searching by domain, you would search by email address or contact name. In the Rodz webhook payload, if the signal includes a contacts array with email addresses, you can match those to HubSpot contacts. You will need to create a parallel set of custom properties on the Contact object and adjust the API calls accordingly.
What happens if Rodz detects a signal for a company not in my CRM?
Your middleware handles this decision. The most common options are: (1) create a new company record in HubSpot automatically, (2) log the miss to a spreadsheet or Slack channel for manual review, or (3) silently discard it. Most teams start with option 2 so they can evaluate whether those companies belong in their CRM before auto-creating records.
How do I handle HubSpot API rate limits?
HubSpot’s private app API allows 100 requests per 10 seconds for OAuth tokens and 150,000 requests per day. For most Rodz users, signal volume is well within these limits. However, if you run a large signal configuration that fires hundreds of events per hour, implement exponential backoff in your middleware. When you receive a 429 response from HubSpot, wait for the duration specified in the Retry-After header before retrying.
Can I use this integration with HubSpot Operations Hub instead of external middleware?
Yes, if you have Operations Hub Professional or Enterprise, you can use custom-coded workflow actions to receive and process webhooks directly inside HubSpot. This eliminates the need for external middleware. However, the custom-coded actions have execution time limits (20 seconds) and limited Node.js library access. For complex transformations, external middleware is still the more flexible option.
Is there a way to filter which signals get synced to HubSpot?
Absolutely. You can filter at two levels. First, when registering your Rodz webhook, you can specify which signal types to subscribe to in the events array. Second, your middleware can include conditional logic that only forwards certain signal types, certain industries, or companies above a certain employee count. For example, you might only sync funding signals above 1M EUR and hiring signals with more than 10 open positions. This keeps your CRM focused on high-value signals and avoids noise.
How do I monitor the integration after it is live?
Build a simple monitoring layer. Log every webhook received, every HubSpot API call made, and every error encountered. If you are using Make, the execution history provides this automatically. For a custom server, send logs to a service like Datadog, LogTail, or even a simple Slack channel. Set up an alert if your middleware stops receiving webhooks for more than an hour during business days, that usually indicates a configuration issue on the Rodz side.
Can I sync historical signals, not just new ones?
Yes. The Rodz API provides a /v1/signals/feed endpoint that returns past signal events with cursor-based pagination. You can write a one-time script that pulls historical signals and pushes them to HubSpot using the same middleware logic. See the API reference for pagination details. Run this script once during initial setup, and let webhooks handle everything going forward.
What to Build Next
With signals flowing into HubSpot, you have the foundation for a signal-driven sales process. Here are logical next steps:
- Build a HubSpot dashboard that shows signal volume by type, by rep, and by pipeline stage. This tells your team which signals are generating the most pipeline.
- Connect Rodz enrichment data to fill in missing company and contact information alongside signals. The enrichment endpoints can populate industry, employee count, technologies used, and other fields that improve segmentation.
- Layer in lead scoring. Use HubSpot’s lead scoring tools to assign points based on signal type and frequency. A company that triggers three different signal types in a month is a hotter prospect than one with a single signal.
- Extend to deals. Instead of only updating company records, create or update deals automatically when high-priority signals fire. A funding signal above 10M could auto-create a deal in the “Opportunity” stage.
For the full list of Rodz API capabilities, explore the interactive API documentation. And if you want to dive deeper into specific signal categories, the guides on financial signals, HR signals, and competitive signals cover each category in detail.