Automated contact enrichment workflow that searches Lusha for target contacts and creates them directly in HubSpot on a recurring schedule.
Docs reference: Lusha Prospecting Search & Enrich API
Search contacts → Filter unrevealed → Enrich (email + phone) → Create in HubSpot → Advance page counterEach run searches Lusha using your configured filters, enriches only contacts not yet revealed (isShown: false), creates them in HubSpot, and saves the current page so the next run picks up where it left off automatically.
| HubSpot Plan | Operations Hub Professional or higher |
| Lusha Plan | API access enabled |
| HubSpot Role | Admin (to create private apps and workflows) |
| Credits per run | 1 credit per enriched contact (up to 50 per run) |
- Go to Settings → Integrations → Private Apps → Create a private app
- Name it (e.g.
Lusha Sync) - Under the Scopes tab add:
crm.objects.contacts.readcrm.objects.contacts.writecrm.objects.companies.readcrm.objects.companies.write
- Click Create app and copy the
pat-...token
- In HubSpot CRM create a new Company named
Lusha Sync Config - Add a custom Number property called
lusha_current_page(default value:0) - Copy the record ID from the URL:
app.hubspot.com/.../company/RECORD_ID
- Go to Automations → Workflows → From scratch → Company-based
- Choose a trigger — the scheduled monthly trigger is the recommended default but any of the following work:
| Trigger | Use case |
|---|---|
| On a schedule (recommended) | Run automatically on a fixed cadence — daily, weekly, or monthly |
| Record created | Run every time a new company is added to HubSpot |
| Property value changes | Run when a specific company property changes (e.g. lifecycle stage) |
| Filter-based enrollment | Run for all companies matching a saved filter (e.g. ICP = true) |
| Manual enrollment | Trigger on-demand for specific accounts |
- Enroll the
Lusha Sync Configcompany record (for scheduled trigger) or set your enrollment criteria
Note: If using a per-company trigger (e.g. record created), remove the pagination logic from the code and pass the company domain dynamically instead — see Using HubSpot Properties in the CONFIG Block below.
- Inside the workflow click + → Custom code → Node.js 20.x
- In the Secrets section add:
api_key→ your Lusha API keyHUBSPOT_TOKEN→ yourpat-...token from Step 1
- Paste the template code (
HubSpot_Lusha_Prospecting_API_template.js) into the editor
At the top of the code update the CONFIG section with your values. Only CONFIG_COMPANY_ID and DOMAINS are required — all other filters are optional and can be left as null.
CONFIG_COMPANY_ID: 'YOUR_RECORD_ID', // from Step 2
PAGE_SIZE: 50, // max 50 per run
SENIORITY: [4], // see seniority reference below
DOMAINS: ["salesforce.com", "google.com"]In the custom code action scroll to Output fields and add:
| Field | Type |
|---|---|
contacts_created | Number |
current_page | Number |
next_page | Number |
total_pages | Number |
Click Test at the bottom of the code editor. Select any contact record when prompted (it won't be used). Check the logs for:
Running page: 0
requestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Contacts on this page: 50
New (unrevealed) contacts: 42
Enriched contacts: 42
Created: 38
Skipped - no email: 3
Skipped - duplicate: 1
Skipped - enrich failed: 0
Page 0/9244 done. Next run: page 1Click Review and publish → Turn On. The workflow runs monthly, advancing one page per run automatically. Page counter resets to 0 once all results are processed.
For accepted values, enums, and full schema details refer to the Lusha Prospecting API docs.
| CONFIG key | API field | Type |
|---|---|---|
SENIORITY | seniority | number[] |
DEPARTMENTS_INCLUDE | departments (include) | string[] |
DEPARTMENTS_EXCLUDE | departments (exclude) | string[] |
EXISTING_DATA_POINTS | existing_data_points | string[] |
CONTACT_LOCATIONS | locations | object[] — { continent, country, city, state, country_grouping } |
SIGNALS | signals | object — { names: string[], startDate: "YYYY-MM-DD" } |
EXCLUDE_DNC | excludeDnc | boolean |
INCLUDE_PARTIAL_CONTACT | includePartialContact | boolean |
| CONFIG key | API field | Type |
|---|---|---|
DOMAINS | domains | string[] |
COMPANY_NAMES | names | string[] |
COMPANY_LOCATIONS | locations | object[] — { country, city, state, continent } |
TECHNOLOGIES | technologies | string[] |
MAIN_INDUSTRY_IDS | mainIndustriesIds | number[] |
SUB_INDUSTRY_IDS | subIndustriesIds | number[] |
INTENT_TOPICS | intentTopics | string[] |
COMPANY_SIZES | sizes | object[] — [{ min, max }] |
REVENUES | revenues | object[] — [{ min, max }] |
SIC_CODES | sicCodes | string[] |
NAICS_CODES | naicsCodes | string[] |
| ID | Level |
|---|---|
| 1 | Intern |
| 2 | Entry |
| 3 | Associate |
| 4 | Senior |
| 5 | Manager |
| 6 | Director |
| 7 | Vice President |
| 8 | C-Level / Executive |
| 9 | Partner |
| 10 | Founder |
- Contacts already in HubSpot are matched by email address — duplicates are silently skipped (HTTP 409)
- Contacts already revealed in Lusha (
isShown: true) are skipped to avoid re-charging credits
The workflow tracks progress using the lusha_current_page property on the config record:
| Run | Page | Contacts |
|---|---|---|
| Month 1 | 0 | 1–50 |
| Month 2 | 1 | 51–100 |
| Month 3 | 2 | 101–150 |
| ... | ... | ... |
| Last page | n | Resets to 0 |
To reset and start over, set lusha_current_page back to 0 on the config company record.
The CONFIG block is static by default, but you can reference live HubSpot property values by passing them in as input fields on the custom code action.
When is this useful?
- You want the
DOMAINSlist driven by company records in HubSpot rather than hardcoded - You want to use a contact owner, lifecycle stage, or custom field to control filter behavior per-run
- You want different teams or accounts to trigger the workflow with their own filters
Step 1 — In the custom code action UI, under "Property to include in code" add:
| Key | Property |
|---|---|
company_domain | Company Domain Name |
target_seniority | Your custom seniority property (if any) |
Step 2 — In the code, read the value from event.inputFields and use it in CONFIG:
exports.main = async (event, callback) => {
// Read HubSpot property passed in as input field
const domainFromRecord = event.inputFields['company_domain'];
// Override CONFIG with live HubSpot value
const domains = domainFromRecord
? [domainFromRecord]
: CONFIG.DOMAINS; // fallback to static list
// Use `domains` instead of CONFIG.DOMAINS in the search body
...
};Only values that map directly to a Lusha API filter are useful here. Passing in an arbitrary HubSpot property won't do anything unless the code uses it to build the search payload.
| HubSpot property | Maps to Lusha filter | Notes |
|---|---|---|
| Company Domain Name | DOMAINS | Most common — search contacts at the enrolled company |
| Custom text (comma-separated domains) | DOMAINS | Split in code: value.split(',').map(d => d.trim()) |
| Custom number (seniority ID) | SENIORITY | Must match Lusha's seniority enum (1–10) |
| Custom text (department) | DEPARTMENTS_INCLUDE | Must match Lusha's department naming exactly |
| Custom text (country) | CONTACT_LOCATIONS | Wrap as [{ country: value }] in the code |
Limitation: Input fields only support flat string/number values — no arrays or objects. Any list (e.g. multiple domains) needs to be stored as comma-separated text and split in the code.