Waterfall enrich HubSpot contacts across Apollo, Clearbit, and PDL 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 (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 keyContent-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 }}equalstrue
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 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 }}equalstrue
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 keyContent-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("+"),
}
}];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 }}"
}
}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
- Click Execute Workflow to run against a recent contact
- Check each Code node's output to verify field merging works correctly
- Confirm the HubSpot contact was updated with the correct source attribution
- 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).
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.