Auto-enrich new HubSpot contacts with Apollo using n8n

medium complexityCost: $0-24/moRecommended

Prerequisites

Prerequisites
  • n8n instance — n8n cloud or self-hosted
  • HubSpot private app token with crm.objects.contacts.read and crm.objects.contacts.write scopes
  • 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).

Trigger polling interval

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 key
    • Content-Type: application/json
  • Body (JSON):
{
  "email": "{{ $json.properties.email }}"
}

The Apollo response includes:

  • person.title — job title
  • person.seniority — seniority level (e.g., "director", "vp")
  • person.organization.name — company name
  • person.phone_numbers[0].sanitized_number — phone
  • person.linkedin_url — LinkedIn profile URL
  • person.departments[] — department tags
  • person.organization.estimated_num_employees — company size range
  • person.organization.industry — industry
Apollo returns nested data

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 }}"
  }
}
Custom properties

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.

Null field handling

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

  1. On the HTTP Request nodes, enable Settings → Retry On Fail with 2 retries and a 3-second wait
  2. Create a separate error workflow that sends you a Slack DM or email when enrichment fails
  3. Link it via Settings → Error Workflow on the main workflow

Step 6: Test and activate

  1. Click Execute Workflow to test with recent contacts
  2. Check the Apollo HTTP node output — verify you see person.title, person.organization, etc.
  3. Check the HubSpot PATCH output — verify a 200 response
  4. Open the contact in HubSpot to confirm fields were updated
  5. 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).
Apollo credit burn

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 = apollo and enrichment_date to 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.