Auto-enrich new HubSpot contacts with Apollo using n8n
Install this workflow
Download the n8n workflow JSON and import it into your n8n instance.
contact-enrichment.n8n.jsonPrerequisites
- n8n instance — n8n cloud or self-hosted
- HubSpot private app token with
crm.objects.contacts.readandcrm.objects.contacts.writescopes - Apollo API key from Settings → Integrations → API (available on all paid plans)
- n8n credentials configured for HubSpot (OAuth or API key)
Why n8n?
n8n gives you a visual workflow builder with full control over API calls and field mapping. Unlike Zapier, you can use HTTP Request nodes to call Apollo's API directly without paying for a premium Webhooks feature — it's included on every plan. Self-hosted n8n is free with unlimited executions, making it the most cost-effective option for teams enriching hundreds of contacts per month.
The trade-off is setup time. You'll configure HTTP headers, write JSON bodies, and handle null checks manually. If you want a plug-and-play experience with no API configuration, Zapier's native HubSpot integration is simpler (though more expensive per contact).
Step 1: Set up the HubSpot trigger
Create a new workflow in n8n. Add a HubSpot Trigger node:
- Trigger event: Contact Created
- Authentication: Your HubSpot credential
This fires every time a new contact is created in HubSpot, regardless of source (form, import, API, manual entry).
n8n's HubSpot Trigger uses polling, not webhooks. On n8n cloud, the default interval is 1 minute. Self-hosted defaults to 5 minutes. Adjust in the node settings under Poll Times.
Step 2: Call the Apollo People Enrichment API
Add an HTTP Request node to enrich the contact via Apollo:
- Method: POST
- URL:
https://api.apollo.io/api/v1/people/match - Headers:
x-api-key: Your Apollo API keyContent-Type:application/json
- Body (JSON):
{
"email": "{{ $json.properties.email }}"
}The Apollo response includes:
person.title— job titleperson.seniority— seniority level (e.g., "director", "vp")person.organization.name— company nameperson.phone_numbers[0].sanitized_number— phoneperson.linkedin_url— LinkedIn profile URLperson.departments[]— department tagsperson.organization.estimated_num_employees— company size rangeperson.organization.industry— industry
The enrichment response nests everything under person. In n8n expressions, access fields as $json.person.title, not $json.title. If the email doesn't match anyone, person will be null — add an IF node before the update step to handle this.
Step 3: Filter out empty results
Add an IF node to skip contacts Apollo couldn't match:
- Condition:
{{ $json.person }}is not empty
This prevents the workflow from trying to update HubSpot with null values and wasting API calls.
Step 4: Update the HubSpot contact
Add an HTTP Request node on the "true" branch to write enriched data back to HubSpot:
- Method: PATCH
- URL:
https://api.hubapi.com/crm/v3/objects/contacts/{{ $('HubSpot Trigger').item.json.id }} - Authentication: HubSpot credential
- Body (JSON):
{
"properties": {
"jobtitle": "{{ $json.person.title }}",
"company": "{{ $json.person.organization.name }}",
"phone": "{{ $json.person.phone_numbers[0]?.sanitized_number }}",
"linkedin_url": "{{ $json.person.linkedin_url }}",
"industry": "{{ $json.person.organization.industry }}",
"numemployees": "{{ $json.person.organization.estimated_num_employees }}"
}
}linkedin_url and numemployees may not exist by default in HubSpot. Create custom contact properties in Settings → Properties before running the workflow, or the PATCH request will silently ignore unknown fields.
If Apollo returns a title but no phone number, you'll write null to the phone field. Use n8n expressions with fallback: {{ $json.person.phone_numbers[0]?.sanitized_number || '' }}. Better yet, use a Code node to build the properties object dynamically, only including fields that have values.
Step 5: Add error handling
- On the HTTP Request nodes, enable Settings → Retry On Fail with 2 retries and a 3-second wait
- Create a separate error workflow that sends you a Slack DM or email when enrichment fails
- Link it via Settings → Error Workflow on the main workflow
Step 6: Test and activate
- Click Execute Workflow to test with recent contacts
- Check the Apollo HTTP node output — verify you see
person.title,person.organization, etc. - Check the HubSpot PATCH output — verify a
200response - Open the contact in HubSpot to confirm fields were updated
- Toggle the workflow to Active
Troubleshooting
Cost
- n8n cloud: Starts at $24/mo (2,500 executions). Each enrichment run uses ~4 executions (trigger + HTTP + IF + update).
- Apollo: 1 credit per person enrichment. On the Basic plan ($49/mo), you get 900 credits/month. The Professional plan ($79/mo) includes 2,400 credits/month.
- HubSpot: API calls are free within rate limits (150 requests per 10 seconds).
Every new contact triggers an enrichment, including test contacts and duplicates. Add a filter early in the workflow to skip contacts with personal email domains (gmail.com, yahoo.com, etc.) if you only want to enrich business contacts. This can save 30-50% of your Apollo credits.
Common questions
How often does the n8n HubSpot trigger poll for new contacts?
On n8n cloud, the default polling interval is 1 minute. Self-hosted defaults to 5 minutes but can be configured down to 1 minute. This means enrichment happens within 1-5 minutes of contact creation — fast enough for most workflows, though not instant like a webhook.
How many contacts can I enrich per month on the free self-hosted plan?
There's no execution limit on self-hosted n8n. The bottleneck is Apollo credits: 900/month on the Basic plan ($49/mo), 2,400/month on Professional ($79/mo). At 1 credit per contact, the Basic plan handles ~225 contacts/week.
Can I use n8n's built-in HubSpot node instead of HTTP Request nodes?
Yes, for the trigger and basic updates. The built-in HubSpot node handles OAuth automatically. However, for custom properties (like linkedin_url) or for the Apollo API call, you'll need HTTP Request nodes since there's no native Apollo node in n8n.
Next steps
- Add ICP filtering — insert a Code node after the trigger to check company size or domain before calling Apollo
- Track enrichment status — set a custom HubSpot property like
enrichment_source = apolloandenrichment_dateto track which contacts were enriched - Enrich with company data — use Apollo's Organization Enrichment endpoint for deeper firmographic data on the associated company
Looking to scale your AI operations?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.