HubSpot Lead Pusher - CRM Contact Import Automation
Push scraped leads directly into HubSpot CRM as contacts, companies, and deals. Feed in lead data from any Apify actor or paste it manually, and HubSpot Lead Pusher handles the mapping, deduplication, and batch upsert via the HubSpot API. A built-in dry-run mode lets you preview exactly what will be created before committing anything to your CRM.
Maintenance Pulse
90/100Cost Estimate
How many results do you need?
Pricing
Pay Per Event model. You only pay for what you use.
| Event | Description | Price |
|---|---|---|
| lead-pushed | Charged per lead pushed to HubSpot. Includes company creation, contact mapping, deal association, and verification. | $0.10 |
Example: 100 events = $10.00 · 1,000 events = $100.00
Documentation
Push scraped leads directly into HubSpot CRM as contacts, companies, and deals. Feed in lead data from any Apify actor or paste it manually, and HubSpot Lead Pusher handles the mapping, deduplication, and batch upsert via the HubSpot API. A built-in dry-run mode lets you preview exactly what will be created before committing anything to your CRM.
HubSpot Lead Pusher bridges the gap between web scraping and CRM action. Instead of manually exporting CSV files and importing them into HubSpot, this actor automates the entire pipeline. It accepts lead data from Apify datasets (output from actors like Website Contact Scraper, B2B Lead Gen Suite, or Google Maps Lead Enricher) or inline JSON, maps each field to the correct HubSpot property, and pushes everything in optimized batches of up to 100 records per API call.
What Does It Do
HubSpot Lead Pusher takes structured lead data and creates or updates three types of HubSpot CRM objects:
-
Companies -- Created from domain names. The actor normalizes URLs, extracts the root domain, and maps fields like company name, phone, industry, address, city, state, country, LinkedIn page, and Facebook page to HubSpot company properties. Companies are upserted by domain, so running the actor twice with the same data updates existing records rather than creating duplicates.
-
Contacts -- Created from email addresses found in the lead data. The actor extracts contacts from both the structured
contactsarray (with name, title, phone) and standaloneemailsfields. Each contact gets mapped with first name, last name, job title, phone, company name, website, lifecycle stage, and lead status. Contacts are upserted by email address for deduplication. -
Deals -- Optionally created for each lead. Deals are named after the company (e.g., "Lead: Apify") and placed in the pipeline stage you specify. Lead scores from upstream qualification actors can be mapped to the deal amount field for pipeline prioritization.
After creating all objects, the actor automatically associates contacts with their parent companies using the HubSpot v4 associations API, so your CRM relationships are properly linked from the start.
Why Use This on Apify
Running HubSpot Lead Pusher on Apify gives you a serverless, schedulable CRM integration without writing code or maintaining infrastructure. You can chain it with other Apify actors to build fully automated lead generation pipelines: scrape websites for contacts, qualify and score the leads, then push the best ones directly into your HubSpot pipeline. Apify Schedules let you run these pipelines daily or weekly so your CRM stays current without manual intervention.
Unlike HubSpot's native import tool, this actor handles the field mapping automatically, normalizes domains and emails, splits full names into first and last name fields, and translates lead grades (A/B/C/D) into HubSpot lead statuses (Open, In Progress, Unqualified). It also works entirely through the API, so there is no file upload step and no waiting for background processing.
Key Features
- Batch upsert -- Companies are upserted by domain and contacts by email, preventing duplicates even across multiple runs
- Automatic field mapping -- Translates scraped lead data into HubSpot properties with domain normalization, name splitting, and grade-to-status conversion
- Dry-run preview -- See the exact HubSpot properties that would be created without touching your CRM, useful for verifying mappings before committing
- Contact-to-company associations -- Automatically links contacts to their parent company in HubSpot after creation
- Deal creation -- Optionally create pipeline deals for each lead with configurable deal stage
- Flexible input -- Accept leads inline as JSON or pull them from any Apify dataset by ID
- Rate limit handling -- Built-in retry logic with exponential backoff for HubSpot API rate limits
- Pipeline compatibility -- Works with output from Website Contact Scraper, B2B Lead Gen Suite, Google Maps Lead Enricher, and any custom lead format
How to Use
-
Get your HubSpot access token -- Go to HubSpot Settings > Integrations > Private Apps and create a private app with scopes:
crm.objects.contacts.write,crm.objects.companies.write, andcrm.objects.deals.write. Copy the access token. -
Prepare your lead data -- Either paste leads directly into the "Leads (inline data)" JSON field, or provide an Apify Dataset ID from a previous scraping run. Each lead should have at least a
domainoremailfield. -
Configure what to create -- Choose whether to create companies, contacts, and/or deals. Set the lifecycle stage for contacts (default: "lead") and the deal stage for deals (default: "appointmentscheduled").
-
Run a dry run first -- Leave the "Dry run" checkbox enabled (it is on by default) to preview the mapped HubSpot properties without pushing anything. Review the output dataset to confirm the field mapping looks correct.
-
Push to HubSpot -- Uncheck "Dry run", enter your HubSpot access token, and run again. The actor will create/update all records and report the results.
-
Check results -- Each lead produces one output record showing the status (success, partial, or error) for the company, contacts, and deal, along with any error messages.
Input Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
hubspotAccessToken | string | No | -- | HubSpot private app access token. Leave empty to run in dry-run mode. |
leads | array | No | -- | Lead data as JSON array. Each object should have at least a domain or email. |
datasetId | string | No | -- | Apify dataset ID to pull leads from (alternative to inline leads). |
createCompanies | boolean | No | true | Create or update HubSpot company records from lead domains. |
createContacts | boolean | No | true | Create or update HubSpot contact records from lead emails. |
createDeals | boolean | No | false | Create a HubSpot deal for each lead. |
dealStage | string | No | appointmentscheduled | HubSpot pipeline stage for new deals. |
lifecycleStage | string | No | lead | HubSpot lifecycle stage for new contacts (subscriber, lead, marketingqualifiedlead, salesqualifiedlead, opportunity, customer). |
dryRun | boolean | No | true | Preview mapped data without pushing to HubSpot. |
You must provide either leads or datasetId. If both are provided, inline leads take priority.
Input Examples
Quick dry-run test with inline leads:
{
"leads": [
{
"domain": "apify.com",
"companyName": "Apify",
"emails": ["[email protected]"],
"contacts": [{"name": "Jan Curn", "title": "CEO", "email": "[email protected]"}],
"score": 85,
"grade": "A"
}
],
"dryRun": true
}
Chain with B2B Lead Gen Suite dataset:
{
"hubspotAccessToken": "pat-na1-xxxx",
"datasetId": "abc123XYZ",
"createCompanies": true,
"createContacts": true,
"createDeals": true,
"dealStage": "qualifiedtobuy",
"lifecycleStage": "marketingqualifiedlead",
"dryRun": false
}
Contacts only (skip companies and deals):
{
"hubspotAccessToken": "pat-na1-xxxx",
"leads": [
{"email": "[email protected]", "companyName": "Example Inc"},
{"email": "[email protected]", "companyName": "Test Corp"}
],
"createCompanies": false,
"createContacts": true,
"createDeals": false,
"dryRun": false
}
Input Tips
- Omitting the access token automatically triggers dry-run mode, even if the
dryRuncheckbox is unchecked. - The
dealStagevalue must match an existing stage in your HubSpot pipeline. Common values:appointmentscheduled,qualifiedtobuy,presentationscheduled,decisionmakerboughtin,contractsent,closedwon,closedlost. - Use
lifecycleStage: "salesqualifiedlead"for high-scoring leads (grade A/B) and"lead"for lower grades to segment your CRM automatically.
Output Example
Each item in the output dataset represents one lead that was processed:
{
"domain": "apify.com",
"status": "success",
"dryRun": false,
"company": {
"id": "12345678901",
"name": "Apify",
"domain": "apify.com",
"status": "created"
},
"contacts": [
{
"id": "98765432101",
"email": "[email protected]",
"name": "Jan Curn",
"status": "created"
},
{
"id": "98765432102",
"email": "[email protected]",
"name": "",
"status": "created"
}
],
"deal": {
"id": "55566677701",
"name": "Lead: Apify",
"status": "created"
},
"associations": {
"contactToCompany": 2
},
"errors": []
}
In dry-run mode, all id fields are null and the status is "dry_run". The properties object is included in dry-run output so you can inspect the exact HubSpot fields that would be set.
Output Fields
| Field | Type | Description |
|---|---|---|
domain | string | Lead domain or "unknown" if no domain provided |
status | string | Overall result: success (all objects created), partial (some failed), error (all failed), or dry_run |
dryRun | boolean | Whether this was a dry-run preview |
company | object/null | Company upsert result with id, name, domain, status. Null if companies disabled |
contacts | array | Array of contact results, each with id, email, name, status |
deal | object/null | Deal creation result with id, name, status. Null if deals disabled |
associations.contactToCompany | number | Number of contact-to-company associations created for this lead |
errors | array | Error messages for any failed operations |
Use via API
You can run HubSpot Lead Pusher programmatically using the Apify API. This is useful for integrating it into automated pipelines where an upstream actor scrapes leads and this actor pushes them to CRM.
Python
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("ryanclinton/hubspot-lead-pusher").call(run_input={
"hubspotAccessToken": "pat-na1-xxxx",
"datasetId": "abc123XYZ", # from a previous scraping run
"createCompanies": True,
"createContacts": True,
"createDeals": True,
"dealStage": "qualifiedtobuy",
"lifecycleStage": "marketingqualifiedlead",
"dryRun": False,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
status = item["status"]
domain = item["domain"]
contacts_count = len(item.get("contacts", []))
print(f"{domain}: {status} ({contacts_count} contacts)")
JavaScript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('ryanclinton/hubspot-lead-pusher').call({
hubspotAccessToken: 'pat-na1-xxxx',
datasetId: 'abc123XYZ',
createCompanies: true,
createContacts: true,
createDeals: true,
dealStage: 'qualifiedtobuy',
lifecycleStage: 'marketingqualifiedlead',
dryRun: false,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach(item => {
console.log(`${item.domain}: ${item.status} (${item.contacts.length} contacts)`);
});
cURL
curl "https://api.apify.com/v2/acts/ryanclinton~hubspot-lead-pusher/runs?token=YOUR_APIFY_TOKEN" \
-X POST \
-H "Content-Type: application/json" \
-d '{
"hubspotAccessToken": "pat-na1-xxxx",
"datasetId": "abc123XYZ",
"createCompanies": true,
"createContacts": true,
"createDeals": true,
"dryRun": false
}'
How It Works
HubSpot Lead Pusher follows a four-step pipeline to ensure all CRM objects are created in the correct order for association linking:
┌─────────────────────────────────────────────────────────────┐
│ LOAD LEADS (inline JSON or Apify dataset) │
└────────────────────────┬────────────────────────────────────┘
│
┌──────────▼──────────┐
│ Dry Run? │
│ → Map & preview │─── YES → Output mapped properties
│ → No API calls │
└──────────┬──────────┘
│ NO
┌──────────▼──────────┐
│ STEP 1: Companies │ Batch upsert by domain
│ /companies/upsert │ → companyIdByDomain map
└──────────┬──────────┘
┌──────────▼──────────┐
│ STEP 2: Contacts │ Batch upsert by email
│ /contacts/upsert │ → contactIdByEmail map
└──────────┬──────────┘
┌──────────▼──────────┐
│ STEP 3: Deals │ Batch create (no upsert)
│ /deals/create │ → dealIdByDomain map
└──────────┬──────────┘
┌──────────▼──────────┐
│ STEP 4: Associate │ Link contacts → companies
│ v4 associations │ via HubSpot association API
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ OUTPUT RESULTS │ One record per lead
└─────────────────────┘
Data Mapping Pipeline
The mapper transforms lead data into HubSpot properties through several conversions:
Domain normalization -- URLs like https://www.apify.com/ are stripped to apify.com. The domain is used as the company's unique identifier for upsert deduplication.
Name splitting -- Full names like "Jan Curn" are split into firstname: "Jan" and lastname: "Curn". Multi-part last names are preserved (e.g., "Jan van der Berg" → firstname: "Jan", lastname: "van der Berg").
Grade-to-status conversion -- Lead grades from the B2B Lead Qualifier are translated into HubSpot's native lead status values:
| Lead Grade | HubSpot Lead Status |
|---|---|
| A, A+ | OPEN |
| B, C | IN_PROGRESS |
| D, F | UNQUALIFIED |
Contact priority -- Contacts from the structured contacts[] array are processed first (they have the most detail: name, title, phone). Standalone emails from the emails[] array and single email field are added afterwards, with email-based deduplication to prevent duplicates.
Deal naming -- Deals are named "Lead: {companyName}" using the company name from the lead data, or derived from the domain if no company name is available (e.g., domain apify.com → "Lead: Apify").
HubSpot Field Mapping Reference
Company properties:
| Lead Field | HubSpot Property | Notes |
|---|---|---|
domain / website | domain | Normalized (www stripped, protocol removed) |
companyName | name | Falls back to capitalized domain (e.g., "Apify") |
phones[0] / phone | phone | First phone number |
industry | industry | Direct mapping |
city | city | Direct mapping |
state | state | Direct mapping |
country | country | Direct mapping |
zip | zip | Direct mapping |
socials.linkedin | linkedin_company_page | Full LinkedIn URL |
socials.facebook | facebook_company_page | Full Facebook URL |
Contact properties:
| Lead Field | HubSpot Property | Notes |
|---|---|---|
contacts[].email / emails[] | email | Lowercased, used as upsert key |
contacts[].name | firstname, lastname | Split at first space |
contacts[].title | jobtitle | Direct mapping |
contacts[].phone | phone | Direct mapping |
companyName | company | Company name string |
domain | website | Normalized domain |
lifecycleStage input | lifecyclestage | From actor input parameter |
grade | hs_lead_status | Converted via grade-to-status table |
Deal properties:
| Lead Field | HubSpot Property | Notes |
|---|---|---|
| -- | dealname | "Lead: {companyName}" |
dealStage input | dealstage | From actor input parameter |
| -- | pipeline | Always "default" |
score | amount | Lead score as deal amount (for pipeline sorting) |
Rate Limiting and Batching
The actor processes records in batches of 100 per API call, with a 150ms delay between batches to stay within HubSpot's free-tier rate limits (100 requests per 10 seconds). If HubSpot returns a 429 rate-limit response, the actor retries with exponential backoff:
| Retry | Wait Time |
|---|---|
| 1st | 1 second |
| 2nd | 2 seconds |
| 3rd | 4 seconds |
Authentication failures (401) are not retried. All API requests have a 30-second timeout.
How Much Does It Cost
HubSpot Lead Pusher uses only 256 MB of memory and runs quickly because it makes API calls rather than crawling websites. The main cost factor is the number of leads processed.
| Scenario | Leads | Estimated Time | Free Plan (0.5 GB) | $49 Plan (100 CUs) |
|---|---|---|---|---|
| Quick test (dry run) | 5 | ~5 seconds | ~14,000 runs/month | ~2,800,000 runs/month |
| Small batch | 25 | ~15 seconds | ~4,700 runs/month | ~940,000 runs/month |
| Medium batch | 100 | ~45 seconds | ~1,500 runs/month | ~300,000 runs/month |
| Large batch | 500 | ~3 minutes | ~300 runs/month | ~60,000 runs/month |
One compute unit (CU) equals 1 GB-hour. The actor uses 256 MB (0.25 GB), so you get approximately four times the runs compared to a 1 GB actor. Actual costs depend on HubSpot API response times and retry frequency.
Tips
-
Always run a dry run first. The default input has dry-run enabled for a reason. Review the mapped properties in the output dataset to catch any field mapping issues before pushing real data to your CRM.
-
Use dataset chaining for automated pipelines. Run Website Contact Scraper or B2B Lead Gen Suite first, then pass the output dataset ID to HubSpot Lead Pusher. This lets you build multi-step workflows entirely within Apify.
-
Set lifecycle stage based on lead quality. If you are using B2B Lead Qualifier upstream, map high-grade leads (A/B) as
marketingqualifiedleadorsalesqualifiedleadand lower grades asleadorsubscriberto keep your HubSpot pipeline organized. -
Create deals only for qualified leads. Enable deal creation selectively for high-value prospects. Flooding your pipeline with unqualified deals makes it harder for sales reps to prioritize.
-
Check HubSpot private app scopes. The most common error is a 401 authentication failure caused by missing scopes. Your private app needs
crm.objects.contacts.write,crm.objects.companies.write, andcrm.objects.deals.writeat minimum. Addcrm.objects.contacts.readif you want the token verification step to pass. -
Re-run safely without duplicates. The actor uses HubSpot's batch upsert endpoint, which matches companies by domain and contacts by email. Running the same data twice updates existing records rather than creating duplicates.
-
Use lead score as deal amount. When deals are created, the lead score from B2B Lead Qualifier is mapped to the deal
amountfield. This lets you sort your HubSpot pipeline by lead quality without additional manual scoring.
Limitations
- Deals cannot be upserted. HubSpot does not support upsert for deals (no unique key). Running the actor twice with deals enabled will create duplicate deals. Disable
createDealsfor repeat runs on the same data. - No custom property creation. The actor maps to standard HubSpot properties only. If you use custom properties in your CRM, they will not be populated automatically.
- Company name fallback. When no
companyNameis provided, the actor derives a name from the domain (e.g.,apify.com→ "Apify"). This works for most domains but may produce odd names for domains like123corp.io. - Single pipeline only. Deals are created in the "default" pipeline. Custom pipelines require modifying the deal stage to match a stage in your desired pipeline.
- No contact-to-deal associations. The actor creates contact-to-company associations but does not link contacts or companies to their associated deals.
- Email-only contacts. Standalone emails from the
emails[]field are created as contacts with just an email address and company name -- no first name, last name, or job title. - Batch size limit. Each API call handles up to 100 records. Very large datasets (10,000+ leads) may take several minutes due to rate limiting.
Responsible Use
- Obtain consent before adding contacts. Only push email addresses that were collected with consent or that fall under legitimate business interest. Adding scraped personal emails to a CRM without consent may violate GDPR, CAN-SPAM, or other privacy regulations.
- Respect HubSpot API limits. The actor includes rate limiting, but running many parallel instances against the same HubSpot account may trigger API restrictions.
- Test with dry runs. Always preview data before pushing to production CRM instances. Incorrect mappings can pollute your CRM with bad data that is time-consuming to clean up.
- Keep access tokens secure. HubSpot private app tokens grant write access to your CRM. Never share tokens in public datasets or logs. Use Apify's secret input fields.
FAQ
What HubSpot plan do I need? HubSpot Lead Pusher works with any HubSpot plan, including the free CRM. You need to create a Private App under Settings > Integrations > Private Apps and grant it the required CRM write scopes. Private Apps are available on all HubSpot plans.
What format does the lead data need to be in?
The actor accepts a flexible lead format. At minimum, each lead needs a domain or email field. It also recognizes companyName, emails (array), phones (array), contacts (array of objects with name, title, email), socials (object with linkedin, twitter, facebook), score, grade, address, city, state, country, zip, and industry. This format is compatible with the output of Website Contact Scraper, B2B Lead Gen Suite, and Google Maps Lead Enricher.
Will it create duplicate records in HubSpot? No, for companies and contacts. Companies are upserted by domain and contacts are upserted by email address. If a record with the same domain or email already exists in HubSpot, it will be updated with the new data rather than duplicated. Deals, however, are always created as new records because HubSpot does not support deal upsert by a unique key.
What happens if the HubSpot API rate limits my requests? The actor includes built-in rate limit handling. It spaces batch requests 150ms apart to stay within HubSpot's free-tier limits (100 requests per 10 seconds). If a 429 rate limit response is received, the actor waits with exponential backoff (1s, 2s, 4s) and retries up to 3 times before failing.
Can I use this with data from non-Apify sources?
Yes. Paste your lead data directly into the "Leads (inline data)" JSON input field. As long as each object has a domain or email field, the actor will process it. You do not need to use an Apify dataset.
How are lead grades converted to HubSpot statuses? The actor translates grades from the B2B Lead Qualifier: grade A or A+ becomes "OPEN" (hot lead), grades B and C become "IN_PROGRESS" (needs nurturing), and grades D and F become "UNQUALIFIED". If no grade is provided, no lead status is set.
What happens if some records fail?
The actor processes all records and reports individual results. If a company upsert succeeds but a contact fails, the lead is marked as partial. Only if all operations fail is the lead marked as error. Check the errors array in each output record for specific failure messages.
Integrations
Connect HubSpot Lead Pusher to your existing tools and workflows:
-
Apify Actors -- Chain with Website Contact Scraper to scrape contacts and push them to HubSpot in a single pipeline. Use B2B Lead Gen Suite for comprehensive lead data or Google Maps Lead Enricher for location-based leads.
-
Zapier -- Trigger a Zap when the actor completes to notify your sales team, log results to a spreadsheet, or kick off follow-up sequences in your email tool.
-
Make (Integromat) -- Build automated workflows that run lead scraping actors on a schedule, feed the dataset into HubSpot Lead Pusher, and alert your team via Slack or email when new leads are added to HubSpot.
-
Google Sheets -- Export the actor's output dataset to Google Sheets for a log of every push operation, including which records were created, updated, or errored.
-
API -- Call the actor programmatically via the Apify API. Pass dataset IDs from upstream runs and retrieve push results in JSON format. Ideal for building custom CRM sync pipelines.
-
Webhooks -- Configure Apify webhooks to trigger downstream actions when the push completes, such as notifying Slack, updating a dashboard, or starting a follow-up email sequence.
Related Actors
These actors from ryanclinton on the Apify Store work well with HubSpot Lead Pusher:
| Actor | What It Does | How It Connects |
|---|---|---|
| Website Contact Scraper | Extract emails, phones, and team members from business websites | Feed its dataset directly into HubSpot Lead Pusher |
| B2B Lead Gen Suite | Orchestrate multi-source lead generation with scoring | Output includes scores and grades that map to HubSpot statuses |
| Google Maps Lead Enricher | Enrich Google Maps business listings with contact details | Maps address data maps to HubSpot company properties |
| B2B Lead Qualifier | Score and grade leads based on company signals | Grades (A-F) convert to HubSpot lead statuses automatically |
| Email Pattern Finder | Discover email patterns and predict addresses | Find more contacts to push as HubSpot contacts |
| Bulk Email Verifier | Verify email deliverability before CRM push | Clean your email list before pushing to HubSpot |
How it works
Configure
Set your parameters in the Apify Console or pass them via API.
Run
Click Start, trigger via API, webhook, or set up a schedule.
Get results
Download as JSON, CSV, or Excel. Integrate with 1,000+ apps.
Use cases
Sales Teams
Build targeted lead lists with verified contact data.
Marketing
Research competitors and identify outreach opportunities.
Data Teams
Automate data collection pipelines with scheduled runs.
Developers
Integrate via REST API or use as an MCP tool in AI workflows.
Related actors
Website Contact Scraper
Extract emails, phone numbers, team members, and social media links from any business website. Feed it URLs from Google Maps or your CRM and get structured contact data back. Fast HTTP requests, no browser — scrapes 1,000 sites for ~$0.50.
Email Pattern Finder
Discover the email format used by any company. Enter a domain like stripe.com and detect patterns like [email protected]. Then generate email addresses for any name. Combine with Website Contact Scraper to turn company websites into complete email lists.
Waterfall Contact Enrichment
Find business emails, phones, and social profiles from a name + company domain. Cascades through MX validation, website scraping, pattern detection, and SMTP verification. Free Clay alternative.
B2B Lead Qualifier - Score & Rank Company Leads
Score and rank B2B leads 0-100 by crawling company websites. Analyzes 30+ signals across contact reachability, business legitimacy, online presence, website quality, and team transparency. No AI keys needed.
Ready to try HubSpot Lead Pusher - CRM Contact Import Automation?
Start for free on Apify. No credit card required.
Open on Apify Store