Auto-enrich new HubSpot contacts with Apollo using n8n
Prerequisites
- 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)
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
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.
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
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.