HubSpot Lead Pusher — Auto-Create Contacts, Companies & Deals
Pricing
from $100.00 / 1,000 lead pusheds
HubSpot Lead Pusher — Auto-Create Contacts, Companies & Deals
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.
Pricing
from $100.00 / 1,000 lead pusheds
Rating
0.0
(0)
Developer
Ryan Clinton
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
6 days ago
Last modified
Categories
Share
HubSpot Lead Pusher
What this actually is (plain English)
HubSpot Lead Pusher is a CRM ingestion engine that decides which leads should be pushed, prevents duplicates, and writes clean, fully-associated records into HubSpot in one run.
It replaces CSV imports, Zapier routing logic, and manual CRM cleanup with a single deterministic pipeline.
Quick answers
- Push scraped leads into HubSpot without duplicates — companies are upserted by domain, contacts by email, deals deduped by name in a single run.
- Zapier alternative for HubSpot lead ingestion — replaces multi-step Zaps with one deterministic pipeline that filters, deduplicates, and associates records in one run.
- Filter bad leads before pushing to HubSpot — pre-push
qualityGaterejects incomplete or low-scoring records before any HubSpot API call. Skipped leads are never billed. - Create one deal per company instead of per lead — group leads by domain and attach multiple contacts to a single deal via
mode: 'account-based-push'(orpushStrategy.dealPolicy: 'one-per-company'). - Preview what will be pushed to HubSpot before committing — Simulation Mode (
dryRun: true) returns a structuredexecutionPlanwith predicted creates, skips, and PPE spend. Zero HubSpot calls. - What is a CRM ingestion engine? — a system that takes raw lead data, filters and deduplicates it, and writes clean, structured records into a CRM automatically. This pattern is used in modern lead generation pipelines to automate CRM data ingestion at scale. HubSpot Lead Pusher is a CRM ingestion engine built specifically for HubSpot.
- Best way to push leads into HubSpot automatically — use a system to push leads into HubSpot that filters bad leads, removes duplicates, and syncs clean data in one step. This ensures only high-quality, non-duplicate records enter your HubSpot CRM. HubSpot Lead Pusher does this in a single deterministic run with no Zapier workflows, no CSV uploads, and no manual CRM cleanup.
- Automate HubSpot lead import — use a scheduled workflow to automate HubSpot lead import by pulling new leads, filtering invalid data, deduplicating against existing records, and syncing clean leads into HubSpot automatically. This keeps your HubSpot CRM continuously updated without manual exports or uploads. HubSpot Lead Pusher does this using Apify Schedules (hourly / daily / weekly) with
qualityGatefiltering and domain/email upsert. - Avoid duplicates in HubSpot imports — use unique identifiers like email (for contacts) and domain (for companies) to upsert records instead of creating new ones in HubSpot. This prevents duplicate companies, contacts, and deals from accumulating in your CRM over multiple imports. HubSpot Lead Pusher applies this automatically: companies upserted by domain, contacts by email, and deals deduped by name search before create.
HubSpot Lead Pusher is the execution layer of an automated lead generation pipeline — not just a connector. Feed it raw leads from any source (Apify dataset, scraping output, custom JSON) and it decides which leads are worth pushing, deduplicates against existing HubSpot records, and writes clean, fully-associated companies + contacts + deals into your CRM in one run.
In a single actor invocation, it replaces the work of:
- HubSpot's CSV import tool (no manual column mapping, no upload step, no UI conflict-resolution)
- Zapier / Make routing logic for "should this lead get pushed?" decisions
- Manual CRM hygiene workflows (dedup, association linking, lifecycle staging)
- External orchestration tools for
if score > 80 then create dealrules
The result: raw lead data → production-ready CRM pipeline with full programmable control over quality filtering, deduplication strategy, deal policy, and account-level routing. Every run is deterministic, auditable, and reproducible via the decisionSnapshot block on every record.
How It Works (mental model)
Each lead flows through a deterministic 7-stage pipeline:
- Quality Gate (
qualityGate) — filters out low-quality leads before any HubSpot calls. Skipped leads are NOT billed. - Rule Engine (
pushRules) — applies your CRM strategy: lifecycle stage overrides, deal-creation gates, conditional skips. - Replay Skip (
watchlistName+replayMode) — skips leads already pushed in a prior run, prevents accidental double-billing. - Push Strategy (
pushStrategy) — determines HOW each record type is created: which contacts (all/best-only/role-based), how many deals (one-per-lead/one-per-company/score-threshold), how deep the associations go (minimal/standard/full). - Safe Write Layer (
safety) — enforces hard limits and aborts BEFORE any HubSpot writes if exceeded. - Association Engine — links contacts ↔ companies, contacts ↔ deals, companies ↔ deals via the HubSpot v4 associations API.
- Outcome Analysis — emits per-lead
failureAnalysis, per-accountcohortInsights, run-levelbatchInsights+suiteInsights+changeAnalysis(when watchlist is set).
Every stage produces a structured record. Every decision is replayable from the decisionSnapshot.inputsHash + profileSnapshot.
Guarantees
- No HubSpot writes occur if
safetylimits are exceeded. - Leads filtered by
qualityGateorpushRulesare never billed. - Companies are upserted by domain; contacts by email; deals are deduped by name search before create.
- Every decision is deterministic — same input + same profile + same rules produces the same
decisionSnapshot.inputsHash. - Same lead in a re-run on the same
watchlistNameis never pushed twice (replayMode default). - Failed runs never throw — error rows land in the dataset with structured
failureAnalysis.category, run exits cleanly.
Common problems this solves
-
"How do I push scraped leads to HubSpot automatically?" Pass an Apify
datasetIdfrom any upstream actor (Website Contact Scraper, B2B Lead Gen Suite, Lead Enrichment Pipeline) and the actor handles ingestion, mapping, batching, and association in one run. -
"How do I avoid duplicate companies / contacts / deals in HubSpot?" Companies are upserted by
domain, contacts byemail(HubSpot-native batch upsert). Deals are deduped bydealnamevia a search-then-update pass — existing deals are updated, not duplicated. -
"How do I filter bad leads before pushing to my CRM?"
qualityGaterejects leads missing required fields (email / domain / phone / decision-maker / minimum field count) BEFORE any HubSpot API call. Filtered leads land asrecordType: 'skipped'and are never billed. -
"How do I create one deal per company instead of one per lead?"
mode: 'account-based-push'(or explicitpushStrategy.dealPolicy: 'one-per-company') collapses leads sharing a domain into a single deal with all the company's contacts attached. -
"How do I control what gets pushed without wiring up Zapier / Make?"
pushRulesis a programmable per-lead if/then engine:{ if: { score: { gte: 80 } }, then: { lifecycleStage: 'salesqualifiedlead', createDeals: true } }. Replaces multi-step external orchestration with one input. -
"How do I preview exactly what will happen before committing to HubSpot?" Simulation Mode (
dryRun: trueormode: 'audit-only') emits a structuredexecutionPlanshowing predicted creates, predicted skips by source, andexpectedSpendUsd— zero HubSpot calls, zero PPE charges. -
"How do I prevent runaway input from polluting my CRM?"
safetylimits abort the run BEFORE any HubSpot writes if planned deal count, planned company count, or input duplicate rate exceeds your caps. Error record only; CRM untouched. -
"How do I track CRM health over time?" Set
watchlistNameand the actor persists per-account history across runs. Each summary recordschangeAnalysis(newAccounts / improved / degraded) andrunDelta(push quality movement vs prior run).
Who this is for
- RevOps teams automating CRM ingestion from scraping pipelines on a schedule
- Growth teams running multi-source lead-gen flows and pushing qualified output into HubSpot
- Agencies managing outbound workflows for multiple clients with per-client quality gates and rules
- Builders replacing fragile Zapier / Make multi-step Zaps with one deterministic actor invocation
- ABM operators running account-level prospecting (
account-based-pushmode + cohort insights) - Compliance-sensitive ops needing replayable, auditable CRM writes (
decisionSnapshot.inputsHash)
When to use this actor
Use HubSpot Lead Pusher when you have a list of leads (from any Apify actor or your own data) and you want to push them into HubSpot as companies, contacts, and deals without writing API code or manually exporting CSVs. The actor handles batch upsert, deduplication, association linking, and dry-run preview in one pass.
When NOT to use this actor
| Scenario | Use this actor instead |
|---|---|
| Need to score / qualify leads before push | Lead Scoring Engine — pipe its output into this actor |
| Need to enrich missing fields (phones, decision-makers) | Lead Enrichment Pipeline — run before this actor |
| Need to verify email deliverability before push | Bulk Email Verifier — clean the list, then push |
| Need to discover contacts for a domain | Email Pattern Finder or Website Contact Scraper |
| Pushing to Salesforce, not HubSpot | Salesforce Lead Pusher — same suite, same input shape |
| Need a dialler queue / SDR cadence | This actor produces CRM records; cadence tools (Outreach, Salesloft, Apollo) consume them |
| Custom HubSpot properties beyond the standard set | Standard properties only — custom-property creation is out of scope |
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.
System capabilities
This actor combines four layers that are normally spread across multiple tools (CSV import + Zapier + custom dedup script + HubSpot UI):
1. Ingestion layer
- Accepts leads from any Apify dataset (chain with Website Contact Scraper, B2B Lead Gen Suite, Lead Enrichment Pipeline, Lead Scoring Engine, etc.) or inline JSON
- Normalizes domains (strips
www.,https://, trailing slash, lowercases), emails, and full names - Auto-detects the upstream source actor from input shape and records it in
actorGraph.previous
2. Decision layer
pushRules[]— programmable per-lead if/then engine with stablegte / lte / eq / in / notIn / contains / endsWithoperatorsqualityGate— pre-push filter (requireEmail,requireDomain,requirePhone,requireDecisionMaker,minScore,minFields) — rejected leads land asrecordType: 'skipped', never billedpushStrategy— fine-grained policies (contactPolicy,dealPolicy,companyPolicy,associationDepth) bundled into mode presets- 6 mode presets —
prospect-import/crm-hygiene-sync/signup-gate/event-attendees/account-based-push/audit-only/raw
3. Execution layer
- HubSpot batch upsert (100 records / batch) for companies (by domain) and contacts (by email)
- Search-then-update dedup for deals (HubSpot lacks native deal upsert) —
dealsUpdateddistinct fromdealsCreatedin summary - Three-tier association linking: Contact ↔ Company (always), Contact ↔ Deal (when
associationDepth: 'full'), Company ↔ Deal (whenassociationDepth: 'full') - Per-row PPE billing — charged once per lead pushed, skipped on
errorANDskippedrows - Field overrides (
fieldOverrides.company / contact / deal) for HubSpot accounts using custom property names
4. Intelligence layer
- Simulation Mode (dry-run) — structured
executionPlanpredicts every write before any HubSpot call: companies / contacts / deals × create / total + skips by source + cohort breakdown +expectedSpendUsd - Failure analysis — every failed push classified into
auth / rate-limit / no-domain / no-contacts / validation / duplicate / network / partial-create, each withrecoverableflag andpathToQualifypointing at the right sibling actor - Account intelligence — per-cohort
roles[],decisionMakerCount,decisionCoverage,pushQualityScore(composite of cohort success + decision-maker coverage + multi-contact bonus) - Cross-run memory (when
watchlistNameset) — per-account history persisted across runs, surfaced ascohortInsights.accountMemoryview + summarychangeAnalysis(newAccounts / improved / degraded) - Decision snapshot — every record carries
decisionSnapshot.inputsHash + profileSnapshot + rulesAppliedso a compliance reviewer can reproduce the exact push deterministically - Suite navigation — every record carries
actorGraph(previous → current → ranked next sibling slugs) andnextActions[]wire-format for Dify / n8n / Zapier multi-step flows - Output profiles (
minimal / standard / full / llm) — shape the dataset for CSV consumers, dashboards, audit trails, or AI agents
Additional production hardening
- Rate limit handling — exponential backoff (1s / 2s / 4s) on HubSpot 429s, 30s timeout per request
- Circuit breaker — aborts after 3 consecutive HubSpot batch failures so a bad token / outage doesn't burn through your full lead list
safetyhard limits —maxDealsPerRun,maxCompaniesPerRun,maxContactsPerCompany,abortIfDuplicateRate— abort BEFORE any HubSpot writes if exceeded- Restricted-token graceful degradation — auto-detects scoped tokens that can't open named KV stores; logs warning + skips watchlist features rather than hanging
Choose your mode
Pick the preset that matches your job. The mode bundles createCompanies / createContacts / createDeals / lifecycleStage / dryRun so you don't have to flip them individually. Explicit values for those flags always override the preset.
| Mode | What it bundles | When to use |
|---|---|---|
prospect-import | companies + contacts + deals, lifecycle=lead, all contacts, one deal per lead | Cold outbound import after qualification |
crm-hygiene-sync | companies + contacts only, lifecycle=lead, no deals | Weekly CRM refresh, no new deals |
signup-gate | companies + contacts only, lifecycle=subscriber, minimal associations | Newsletter / signup capture |
event-attendees | companies + contacts + deals, lifecycle=marketingqualifiedlead, full associations | Webinar / event attendee imports |
account-based-push | companies + decision-maker contacts only + one deal per company (deduped across leads sharing a domain), full Contact↔Deal + Company↔Deal associations | ABM workflows where buying committee matters |
audit-only | dry-run forced ON, all flags ON | Field-mapping preview before committing |
raw | no preset — apply user-supplied flags only | Power users / custom workflows |
Control plane — push strategy, rules, quality gate
Beyond mode presets, four advanced inputs let you control exactly how leads get pushed:
Push strategy (pushStrategy)
Fine-grained policies for contacts, deals, companies, and association depth:
{"pushStrategy": {"contactPolicy": "best-only","dealPolicy": "score-threshold","dealScoreThreshold": 75,"companyPolicy": "always","associationDepth": "full"}}
contactPolicy—all(every contact),best-only(top by seniority rank),role-based(only senior titles: CEO/CTO/VP/Director/etc.)dealPolicy—one-per-lead,one-per-company(collapses leads sharing a domain into one deal),one-per-contact,score-threshold(only whenlead.score ≥ dealScoreThreshold),nonecompanyPolicy—always,only-if-new(skip updates on existing companies)associationDepth—minimal(no associations),standard(Contact↔Company),full(adds Contact↔Deal + Company↔Deal)- Deal dedup is automatic in live mode — existing deals matching the company name are updated instead of duplicated. The summary record reports
dealsUpdatedvsdealsCreatedseparately.
Rule engine (pushRules)
Programmable per-lead if/then logic. Replaces "do this in Make/Zapier" with one input:
{"pushRules": [{ "if": { "score": { "gte": 80 } },"then": { "lifecycleStage": "salesqualifiedlead", "createDeals": true },"label": "high-intent" },{ "if": { "emails": { "count": { "eq": 0 } } },"then": { "skip": true, "skipReason": "no_contacts" } },{ "if": { "industry": { "in": ["education", "nonprofit"] } },"then": { "lifecycleStage": "subscriber", "createDeals": false } }]}
- Conditions:
score / grade / emails.count / contacts.count / industry / domainwithgte / lte / eq / in / notIn / contains / endsWithoperators. - Actions:
skip(withskipReason),lifecycleStage,dealStage,createDeals / createCompanies / createContactsoverrides. - Rules evaluate top-down; first
skip:truehalts evaluation for that lead. - Rules that match without
skip:trueapply their overrides cumulatively. - Skipped leads land in the dataset as
recordType: 'skipped'with the rule label that fired — never billed.
Quality gate (qualityGate)
Pre-push filter that prevents garbage from reaching HubSpot and prevents PPE billing on bad leads:
{"qualityGate": {"minScore": 60,"requireEmail": true,"requireDomain": true,"minFields": 5,"requireDecisionMaker": true}}
Failing leads land in the dataset as recordType: 'skipped' with skipReason.code and dataQuality.gateFailReasons[] documenting why. Zero HubSpot API calls, zero PPE charges.
Field overrides (fieldOverrides)
Remap default HubSpot property names — useful when your account uses custom property names:
{"fieldOverrides": {"company": { "industry": "customIndustryField" },"contact": { "jobtitle": "role" },"deal": { "dealname": "opportunityName" }}}
Safety limits (safety)
Hard caps that abort the run BEFORE any HubSpot writes if exceeded — protects against runaway input or upstream bugs:
{"safety": {"maxDealsPerRun": 50,"maxCompaniesPerRun": 200,"maxContactsPerCompany": 5,"abortIfDuplicateRate": 0.3}}
maxDealsPerRun/maxCompaniesPerRun— abort if the planned (post-rules, post-quality-gate) write count would exceed the cap. Catches a bad rule or input shape before it pollutes HubSpot.maxContactsPerCompany— cap contacts pushed per domain (drops surplus contacts, doesn't abort).abortIfDuplicateRate— abort if input has more than this fraction of duplicate domains. Almost always indicates an upstream dedup issue worth surfacing.- On safety abort: actor exits cleanly with a single
recordType: 'error'row carryingfailureType: 'safety-abort',limit, andobserved. Zero CRM writes, zero PPE charges.
Watchlist (watchlistName + replayMode)
Cross-run state for scheduled / repeated runs:
{"watchlistName": "weekly-prospect-sync","replayMode": "skip"}
replayMode: 'skip'(default) skips leads whoseeventIdwas already pushed in a prior run on this watchlist — prevents accidental double-billing.replayMode: 'force'pushes regardless (useful for full refresh runs).- The summary record gains
batchInsights.runDeltashowing pushQuality / successRate / noContactRate / duplicateRate movement vs the previous run. - Each watchlist gets its own named KV store (
hubspot-lead-pusher-state-<name>) bounded at 50K eventIds + 25K accounts. - Suite-aligned alias:
monitorStateKeyaccepts the same value aswatchlistNameso upstream orchestrators can pass one consistent field name acrosshubspot-lead-pusher,salesforce-lead-pusher,lead-scoring-engine,bulk-email-verifier,waterfall-contact-enrichment,phone-number-finder,company-deep-research, andlead-enrichment-pipeline. If both are set,watchlistNamewins. - Closed-loop feedback (
lastAction): pass{ type, takenAt: ISO date, note? }to tell the actor what action you took on this watchlist since the last run. On the next scheduled run the actor compares the prior push outcome against the current one and emitsdecisionMemorywith an inferred outcome (engaged/no-response/no-change/resolved/too-soon-to-tell).
Account memory + change analysis (account-level intelligence)
When watchlistName is set, the actor builds per-account history alongside the eventId set:
- Per-account memory persists
firstSeenAt,lastPushedAt,runCount,contactsPushedTotal,lastOutcome,lastPushQualityScoreper canonical domain. Bounded FIFO at 25K accounts. - Each
cohortInsightsblock in the dataset gains anaccountMemoryview:isReturning,pushQualityDelta(current vs prior), prior outcome, total runs seen. - The summary record gains a
changeAnalysisblock:newAccounts,returningAccounts,accountsImproved,accountsDegraded,newContacts+ plain-English note (e.g. "5 new accounts, 3 improved, 1 degraded since last run."). - ABM workflows: a scheduled run on a stable account list now answers "which accounts got better since last week?" without external joins.
Account graph (per-domain decision-maker coverage)
Cohort insights now include explicit ABM intelligence:
roles[]— list of contact titles seen for the domain (capped 10, deduped).decisionMakerCount— count of contacts whose title matches CEO / CTO / CFO / VP / Director / Head of / etc.decisionCoverage— fraction of contacts in the cohort that are decision-makers (0-1).pushQualityScore(per-account) — composite of cohort success rate (60%), decision-maker coverage (25%), multi-contact bonus (15%). 0-100, withpushQualityLevelband.- Use these to drive routing in Dify:
WHERE cohortInsights.decisionCoverage > 0.5 AND cohortInsights.pushQualityLevel IN ('excellent', 'good')filters to ABM-ready accounts.
Execution plan (predict before you push)
The dry-run summary's simulationStats now includes a structured executionPlan block:
{"executionPlan": {"companies": { "create": 42, "update": 0, "total": 42 },"contacts": { "create": 96, "update": 0, "total": 96 },"deals": { "create": 25, "update": 0, "total": 25 },"skips": { "qualityGate": 14, "rules": 6, "replay": 9, "noDomain": 2, "total": 31 },"accounts": { "unique": 67, "multiContact": 12, "decisionMakerCovered": 41 },"note": "Predicted: 42 companies, 96 contacts, 25 deals. 31 leads skipped before any HubSpot writes. Estimated PPE spend: $13.30.","splitMethodology": "input-shape-only"}}
- Default methodology is
'input-shape-only'— predicts based on the input shape alone, no API calls, no extra cost. updatecounts default to 0 because we can't tell creates from updates without hitting HubSpot's search API in dry-run mode (live mode does perform deal-dedup search).- Use this to validate large scheduled runs BEFORE flipping them live:
expectedSpendUsdplus thenoteanswers "how much will the next run cost?" with no surprises.
Suite contract — same shape across CRM-pusher actors
hubspot-lead-pusher and the sibling salesforce-lead-pusher share the exact same input + output contract — mode, pushStrategy, pushRules, qualityGate, fieldOverrides, safety, watchlistName all behave identically across both actors. Swap the actor ID in your Dify / n8n / Zapier workflow to switch destinations without rewiring.
Simulation Mode (zero-billing pre-run preview)
Set dryRun: true (or mode: 'audit-only') and the actor runs the entire decision pipeline — quality gate, rule engine, push strategy, association planning — without touching HubSpot. The dataset returns:
- Mapped HubSpot properties for every lead (the exact JSON that would be sent)
- Per-lead
dataQualityblock showing which fields are present / missing - Per-lead
nextActions[]showing which sibling actor would fix each gap - A summary record with a structured
simulationStats.executionPlan:
{"executionPlan": {"companies": { "create": 42, "update": 0, "total": 42 },"contacts": { "create": 96, "update": 0, "total": 96 },"deals": { "create": 25, "update": 0, "total": 25 },"skips": { "qualityGate": 14, "rules": 6, "replay": 9, "noDomain": 2, "total": 31 },"accounts": { "unique": 67, "multiContact": 12, "decisionMakerCovered": 41 },"note": "Predicted: 42 companies, 96 contacts, 25 deals. 31 leads skipped before any HubSpot writes. Estimated PPE spend: $13.30."}}
Zero HubSpot calls. Zero PPE charges. Full visibility. Use Simulation Mode to validate field mapping, tune the quality gate, calibrate push rules, and confirm the expected spend on a 5,000-lead schedule before flipping it live.
Account-Level Intelligence (ABM)
Leads sharing a domain are grouped into accounts. For each account the actor computes:
leadsInCohort+contactsInCohort— raw countsroles[]— every contact title seen for the domain (capped 10, deduped)decisionMakerCount+decisionCoverage— how many of the contacts are CEO / CTO / VP / Director / Head-of (decisionCoverage = fraction 0-1)pushQualityScore+pushQualityLevel— composite per-account quality (0-100, bandedexcellent / good / fair / poor / unsendable) from cohort success rate (60%) + decision-maker coverage (25%) + multi-contact bonus (15%)recommendedApproach—individual/account-based/avoid/unknownaccountMemoryview (whenwatchlistNameis set) —firstSeenAt,lastPushedAt,runCount, prior outcome,qualityDeltavs prior run,isReturningboolean
This unlocks ABM workflows directly inside the actor:
- Use
mode: 'account-based-push'to roll up leads-per-domain into one deal per company with role-based contact selection (decision-makers only) and full Contact↔Deal + Company↔Deal associations - Filter downstream Dify / n8n nodes on
cohortInsights.decisionCoverage > 0.5to surface ABM-ready accounts - Set
watchlistName: 'abm-q4'and the summary record'schangeAnalysisanswers "which target accounts improved since last week's push?" without external joins
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. |
mode | string | No | raw | Job preset: prospect-import / crm-hygiene-sync / signup-gate / event-attendees / audit-only / raw. Sets createCompanies / createContacts / createDeals / lifecycleStage / dryRun together. Explicit per-flag values override the preset. |
outputProfile | string | No | standard | Output shape: minimal (IDs + status only), standard (full result), full (incl. audit/replay blocks), llm (decision-friendly subset for AI agents). |
systemMode | boolean | No | false | Auto-enable includeBatchInsights + includeCohortInsights. Use for recurring/scheduled workflows. |
includeBatchInsights | boolean | No | false | Append a run-level summary record with success rate, failure category distribution, and sibling-actor recommendations. |
includeCohortInsights | boolean | No | false | Group leads by canonical domain and emit per-domain push outcome on each record + a COHORT_INSIGHTS key in KV. Useful for ABM workflows. |
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": ["hello@apify.com"],"contacts": [{"name": "Jan Curn", "title": "CEO", "email": "jan@apify.com"}],"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": "jane@example.com", "companyName": "Example Inc"},{"email": "john@test.com", "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": "jan@apify.com","name": "Jan Curn","status": "created"},{"id": "98765432102","email": "hello@apify.com","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 |
|---|---|---|
schemaVersion | string | Output shape version (additive across minor versions) |
recordType | string | result (per-lead row), summary (run rollup), or error |
entityId | string | Stable cross-suite canonical id (sha256-derived). Suite-aligned name; same join key as salesforce-lead-pusher, waterfall-contact-enrichment, phone-number-finder, bulk-email-verifier, company-deep-research, lead-scoring-engine, and lead-enrichment-pipeline. |
eventId | string | Legacy alias of entityId (same value). Kept for back-compat with existing downstream pipelines + watchlist diffs. |
signalIndependence | object | { score, distinctSourceCount, totalComponentCount, interpretation, warning? }. Catches the "looks like 6 quality signals but really 1 dominating" trap. Aligned with the rest of the suite. |
counterfactual | object | { droppedComponent, withoutThisSignal: { score, level, pushable }, interpretation }. Drops the highest-weight quality signal and recomputes — tells you whether the CRM-write decision is load-bearing on a single quality dimension. |
decisionMemory | object|null | Closes the feedback loop when lastAction is provided as input. { outcome: 'engaged' | 'no-response' | 'no-change' | 'resolved' | 'too-soon-to-tell', daysSinceAction, confidence, inferenceMethod, epistemicStatus }. Honest: only push-result delta is observable. |
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 |
summary | string | Plain-English one-line outcome (LLM-friendly, ≤280 chars) |
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 |
pipelineState | object | What HAS been done already: enriched, emailVerified, intentChecked, crmSynced, deduped (booleans, detected from input fields) |
actorGraph | object | Suite navigation: previous (upstream slug detected from source/sourceUrl), current (this actor), next[] (ranked sibling slugs to chain) |
decisionTrace | array | Flat enum codes of which rules fired (mode:prospect-import, upserted_company, upserted_contacts:3, created_deal, linked_contacts_to_company:2, status:success) |
nextActions | array | Ranked sibling-actor calls with inputHint and blocking flag — wire-format for Dify / n8n / Zapier multi-step flows |
recommendedAction | object | Top next step: actionId, label, owner, eta, targetActorSlug |
task | object | Universal task schema: id, kind, target, payload, dryRun, shouldAct |
failureAnalysis | object/null | Root-cause attribution when push failed: category (auth/rate-limit/no-domain/no-contacts/validation/duplicate/network/partial-create), severity, explainLikeOperator (paste-ready), recoverable, pathToQualify |
improvementSuggestions | array | Top 3 pre-push improvements that would lift outcome quality (each with targetActorSlug) |
decisionSnapshot | object | Audit-replay primitive: eventId, inputsHash, rulesApplied, profileSnapshot, replayable: true |
cohortInsights | object | Per-domain rollup (when includeCohortInsights=true): leadsInCohort, multiContactCompany, cohortSuccessRate, recommendedApproach: 'individual' / 'account-based' / 'avoid' |
dataQuality | object | Lead-level quality assessment: score (0-100), fieldsPresent / fieldsTotal, missingFields[], confidence: 'high' / 'medium' / 'low', passedGate, gateFailReasons[] |
skipReason | object/null | Present on recordType: 'skipped' records: source (quality-gate / rule-engine / replay-skip / strategy-policy), code, message, rule (label of the matching rule, if any) |
rulesApplied | array/null | Push rules whose conditions matched this lead, in evaluation order |
replayed | boolean/null | True when this lead was skipped because its eventId already appeared in the watchlist's processed set |
associations.contactToDeal / associations.companyToDeal | number | Counts of additional association links created (only with associationDepth: 'full') |
suiteInsights (summary record only) | object | missingSteps[], recommendedPipeline[] ranked sibling-actor slugs, per-need lead counts |
simulationStats (dry-run summary only) | object | expectedPushable, predictedDuplicates, predictedNoContacts, predictedNoDomain, predictedQualityGateFails, predictedRuleSkips, expectedSpendUsd |
batchInsights.runDelta (summary, when watchlistName set) | object | pushQualityScoreChange, successRateChange, noContactRateChange, duplicateRateChange, note plain-English |
cohortInsights.roles / decisionMakerCount / decisionCoverage / pushQualityScore / pushQualityLevel | various | Account graph: contact title list, decision-maker count, decision-maker coverage 0-1, per-account push quality score 0-100 + level band |
cohortInsights.accountMemory (when watchlistName set) | object | Per-account history view: firstSeenAt, lastPushedAt, runCount, contactsPushedTotal, lastOutcome, pushQualityDelta, isReturning |
simulationStats.executionPlan (dry-run summary only) | object | Predicted writes per record type (companies / contacts / deals × create / update / total) + skips by source + accounts (unique / multiContact / decisionMakerCovered) + note plain-English |
changeAnalysis (summary, when watchlistName set) | object | newAccounts, returningAccounts, accountsImproved, accountsDegraded, accountsUnchanged, newContacts + plain-English note |
safetyAborted (error record only) | object | Present if a safety limit aborted the run: triggered, reason, limit (maxDealsPerRun / maxCompaniesPerRun / abortIfDuplicateRate), observed |
Stable enum tokens
These enums are additive only — values are never renamed or repurposed within a major version:
recordType:result,summary,error,skippedstatus:success,partial,error,dry_run,skippedfailureAnalysis.category:auth,rate-limit,validation,schema-mismatch,not-found,duplicate,network,partial-create,no-domain,no-contacts,unknownfailureAnalysis.severity:critical,high,medium,low,nonerecommendedAction.owner:user,sdr,ops,systemtask.kind:crm-push,crm-skip,crm-errorcohortInsights.recommendedApproach:individual,account-based,avoid,unknownskipReason.source:quality-gate,rule-engine,replay-skip,strategy-policydataQuality.confidence:high,medium,lowpushStrategy.contactPolicy:all,best-only,role-basedpushStrategy.dealPolicy:none,one-per-lead,one-per-company,one-per-contact,score-thresholdpushStrategy.associationDepth:minimal,standard,fullcohortInsights.pushQualityLevel:excellent,good,fair,poor,unsendableaccountMemory.lastOutcome:success,partial,error,skippedfailureType(error record):invalid-input,safety-abort,auth,network
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 ApifyClientclient = 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" |
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.
-
Sort by lead grade in HubSpot. Deal
amountis left blank so it doesn't pollute your HubSpot pipeline revenue forecasting. Sort or filter onhs_lead_status(Open / In Progress / Unqualified, derived from grade A/B/C/D/F) instead.
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.
Use in Dify
Drop this actor into Dify workflows via the Apify plugin's Run Actor node. Each lead returns scored, classified, and routed as structured JSON — success / partial / error / dry_run plus the failureAnalysis.category enum your downstream node branches on. A "Zapier — HubSpot Create Contact" step pointed at the same lead returns one record with no decision shape; this returns push outcomes plus the next sibling actor to call.
- Actor ID:
ryanclinton/hubspot-lead-pusher - Sample input (cold-outbound import after Lead Scoring Engine):
{"mode": "prospect-import","datasetId": "abc123XYZ","hubspotAccessToken": "pat-na1-xxxx","outputProfile": "llm","systemMode": true,"dryRun": false}
Branching example — route on push outcome
A Dify if/else node can branch on status directly:
IF status == "success" → log + notify SDR (lead in CRM, ready to work)ELIF status == "partial" → branch on failureAnalysis.category├── "no-contacts" → call ryanclinton/email-pattern-finder (next[0])├── "validation" → write to review-queue└── default → call ryanclinton/bulk-email-verifierELIF status == "error" → branch on failureAnalysis.category├── "auth" → halt + alert ops (token issue)├── "rate-limit" → wait 60m + retry└── "no-domain" → call ryanclinton/lead-enrichment-pipeline (next[0])ELIF status == "dry_run" → write to preview-channel for review
The actor's nextActions[] array gives you the sibling-actor slug + inputHint directly — no JSONata, no parsing. A Dify "Run Actor" node can read record.nextActions[0].actor and record.nextActions[0].inputHint to chain the next call without any prompt logic.
Modes Dify workflows can leverage
mode: 'audit-only'— every lead returns mapping preview, zero CRM writes (use to validate field mapping in a Dify dry-run loop before flipping a workflow live)mode: 'crm-hygiene-sync'+systemMode: true— weekly refresh with batch insights surfacing list-quality drift over timeoutputProfile: 'llm'— strips audit/replay blocks, keepsrecommendedAction/failureAnalysis/actorGraph/decisionTrace/nextActions— perfect token budget for AI agent tool-calls
Structured action arrays — usable verbatim
nextActions[] and improvementSuggestions[] are emitted as wire-ready arrays — no LLM rewriting required. Each item carries the sibling actor's slug, the input hint, and a blocking: boolean flag downstream agents branch on to decide between halting and parallelizing. The arrays are deterministic: same input + same mode always produces the same array. decisionTrace[] is a flat enum array (mode:prospect-import, upserted_company, created_deal, etc.) you can branch on without parsing object structure.
Why not Zapier or Make?
Zapier and Make require multi-step workflows (often 8-15 steps per Zap) to do what this actor does in one invocation:
- A 12-step Zap to filter leads, decide what to push, deduplicate, and create associations
- Per-step billing (Zapier ≈ $0.025 / task) — a single 200-lead run = 1,600+ Zap tasks
- Logic fragmented across multiple steps with no single source of truth
- No pre-run preview — Zaps execute one record at a time, you find out what happened after the fact
- Filter steps have no concept of "skip but don't bill" — they consume tasks regardless
This actor collapses the entire decision + execution + cleanup loop into one programmable run with predictable PPE billing and a structured execution plan you can preview before committing.
Capability comparison
| Capability | This actor | HubSpot CSV import | Zapier / Make multi-step Zap |
|---|---|---|---|
| Field mapping | Automatic (domain normalisation, name splitting, grade-to-status) | Manual column-by-column per import | Per-Zap mapping, one record at a time |
| Lead filtering before push | qualityGate — never bills filtered leads | None — manual cleanup AFTER import | Separate filter step (still bills) |
| Decision logic (if score > 80 then…) | pushRules[] programmable in one input | None — manual lifecycle tagging | Multi-step path/branch logic per Zap |
| Batch upsert with dedupe | Yes — companies by domain, contacts by email, deals by name search, 100/batch | Yes, manual conflict-resolution UI | No — one record per task |
| Deals + Contact↔Deal + Company↔Deal associations | Yes — full association linking in one run | No — separate import + manual association | Yes, but each link as a separate Zap step |
| Pre-run execution plan | executionPlan predicts every write before any HubSpot call, zero billing | No — imports go straight to CRM | No — Zaps execute one record at a time |
| Failure root-cause attribution | failureAnalysis.category per record | Generic "import error" log | Per-task error string |
| Decision-replay snapshot (audit) | decisionSnapshot.inputsHash + profileSnapshot | No | No |
| Cross-run change tracking | changeAnalysis (newAccounts / improved / degraded) per watchlist | No | Requires separate analytics setup |
| Account-based push (one deal per company) | mode: 'account-based-push' built-in | No — manual rollup per import | Multi-step grouping logic per Zap |
| Schedulable | Apify Schedules, no extra subscription | No — manual upload | Yes, but billed per task per record |
| Per-record cost | $0.10 PPE per lead pushed (skipped on error AND skipped rows) | Free (manual time cost) | $0.025+ per Zap task × multiple tasks per lead |
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 |