Waterfall enrich HubSpot contacts across Apollo, Clearbit, and PDL using n8n

high complexityCost: $0-24/mo

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 (Settings → Integrations → API)
  • Clearbit API key (API → API Keys in dashboard)
  • People Data Labs API key (from PDL dashboard)
  • n8n credential configured for HubSpot

Step 1: Trigger on new contacts

Add a HubSpot Trigger node:

  • Trigger event: Contact Created
  • Authentication: Your HubSpot credential

Alternatively, use a Schedule Trigger to batch-enrich on a schedule (see Recipe 5 for the batch pattern).

Step 2: Call Apollo People Enrichment (first provider)

Add an HTTP Request node:

  • Method: POST
  • URL: https://api.apollo.io/api/v1/people/match
  • Headers:
    • x-api-key: Your Apollo API key
    • Content-Type: application/json
  • Body:
{
  "email": "{{ $json.properties.email }}"
}

Step 3: Track which fields are still missing

Add a Code node (Run Once for All Items) to evaluate what Apollo returned and flag gaps:

const person = $input.first().json.person;
const contactId = $('HubSpot Trigger').first().json.id;
const email = $('HubSpot Trigger').first().json.properties.email;
 
const fields = {
  jobtitle: person?.title || null,
  company: person?.organization?.name || null,
  phone: person?.phone_numbers?.[0]?.sanitized_number || null,
  linkedin_url: person?.linkedin_url || null,
  industry: person?.organization?.industry || null,
  seniority: person?.seniority || null,
};
 
const missingFields = Object.entries(fields)
  .filter(([_, v]) => !v)
  .map(([k]) => k);
 
return [{
  json: {
    contactId,
    email,
    fields,
    missingFields,
    source: "apollo",
    needsFallback: missingFields.length > 0,
  }
}];

Step 4: Branch to Clearbit if fields are missing

Add an IF node:

  • Condition: {{ $json.needsFallback }} equals true

On the true branch, add an HTTP Request node for Clearbit:

  • Method: GET
  • URL: https://person.clearbit.com/v2/people/find?email={{ $json.email }}
  • Headers:
    • Authorization: Bearer YOUR_CLEARBIT_API_KEY
Clearbit auth format

Clearbit uses Bearer token auth, not Basic auth. Pass your API key as Bearer sk_... in the Authorization header.

Add a second Code node to merge Clearbit data into any remaining gaps:

const existing = $('Check Missing Fields').first().json;
const clearbit = $input.first().json;
const fields = { ...existing.fields };
 
// Only fill fields that are still null
if (!fields.jobtitle && clearbit.title) fields.jobtitle = clearbit.title;
if (!fields.company && clearbit.employment?.name) fields.company = clearbit.employment.name;
if (!fields.seniority && clearbit.employment?.seniority) fields.seniority = clearbit.employment.seniority;
if (!fields.linkedin_url && clearbit.linkedin?.handle) {
  fields.linkedin_url = `https://linkedin.com/in/${clearbit.linkedin.handle}`;
}
 
const stillMissing = Object.entries(fields)
  .filter(([_, v]) => !v)
  .map(([k]) => k);
 
return [{
  json: {
    ...existing,
    fields,
    missingFields: stillMissing,
    source: stillMissing.length < existing.missingFields.length ? "apollo+clearbit" : existing.source,
    needsFallback: stillMissing.length > 0,
  }
}];

Step 5: Fall back to People Data Labs for remaining gaps

Add another IF node on the Clearbit output:

  • Condition: {{ $json.needsFallback }} equals true

On the true branch, add an HTTP Request node for PDL:

  • Method: POST
  • URL: https://api.peopledatalabs.com/v5/person/enrich
  • Headers:
    • x-api-key: Your PDL API key
    • Content-Type: application/json
  • Body:
{
  "email": "{{ $json.email }}"
}

Add a Code node to merge PDL data:

const existing = $input.first().json;
const pdl = $('PDL Enrichment').first().json.data || $('PDL Enrichment').first().json;
const fields = { ...existing.fields };
 
if (!fields.jobtitle && pdl.job_title) fields.jobtitle = pdl.job_title;
if (!fields.company && pdl.job_company_name) fields.company = pdl.job_company_name;
if (!fields.phone && pdl.phone_numbers?.[0]) fields.phone = pdl.phone_numbers[0];
if (!fields.linkedin_url && pdl.linkedin_url) fields.linkedin_url = pdl.linkedin_url;
if (!fields.industry && pdl.industry) fields.industry = pdl.industry;
 
const sources = [existing.source, "pdl"].filter(Boolean);
 
return [{
  json: {
    ...existing,
    fields,
    source: sources.join("+"),
  }
}];
PDL response structure

People Data Labs nests the person data under a data key when called via the REST API. Check for both response.data and the top-level response, as the structure varies between direct API calls and SDK usage.

Step 6: Write enriched data to HubSpot

All three branches (Apollo-only, Apollo+Clearbit, Apollo+Clearbit+PDL) converge here. Add a Merge node set to Merge by Position to combine the branches, then an HTTP Request node:

  • Method: PATCH
  • URL: https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.contactId }}
  • Authentication: HubSpot credential
  • Body:
{
  "properties": {
    "jobtitle": "{{ $json.fields.jobtitle }}",
    "company": "{{ $json.fields.company }}",
    "phone": "{{ $json.fields.phone }}",
    "linkedin_url": "{{ $json.fields.linkedin_url }}",
    "industry": "{{ $json.fields.industry }}",
    "enrichment_source": "{{ $json.source }}"
  }
}
Track the source

Write the enrichment_source (e.g., "apollo", "apollo+clearbit", "apollo+clearbit+pdl") to a custom HubSpot property. This tells you which provider filled which contacts — invaluable for evaluating provider ROI.

Step 7: Test and activate

  1. Click Execute Workflow to run against a recent contact
  2. Check each Code node's output to verify field merging works correctly
  3. Confirm the HubSpot contact was updated with the correct source attribution
  4. Toggle the workflow to Active

Cost

  • n8n cloud: Starts at $24/mo. Each waterfall run uses 5-10 executions depending on how many providers are called.
  • Apollo: 1 credit per enrichment ($49/mo Basic = 900 credits)
  • Clearbit: Starts at $99/mo for the Enrichment API. Pricing is volume-based.
  • People Data Labs: $0.03-0.10 per enrichment depending on plan. Pay-as-you-go available.
  • Best case: Apollo matches (1 credit). Worst case: All three providers called (1 Apollo + 1 Clearbit + 1 PDL credit).
Minimize credit spend

The waterfall pattern is designed to minimize cost — you only call Clearbit if Apollo missed fields, and only call PDL if both missed. On average, Apollo covers 60-70% of B2B contacts fully, so you'll only call Clearbit for ~30% and PDL for ~10-15%.

Next steps

  • Add enrichment scoring — track fill rate per provider to evaluate if you're getting value from each
  • Add a dead-letter queue — store contacts that all three providers missed for manual research
  • Reorder providers — if Clearbit consistently fills more fields than Apollo for your ICP, swap their order

Need help implementing this?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.