Enrich new Salesforce contacts with Apollo using n8n
Install this workflow
Download the n8n workflow JSON and import it into your n8n instance.
sf-enrich.n8n.jsonPrerequisites
- n8n instance (cloud or self-hosted)
- Salesforce Connected App with OAuth credentials (Client ID and Secret)
- Apollo API key (from Apollo Settings → Integrations → API)
- n8n credentials configured for Salesforce
Why n8n?
n8n's Salesforce Trigger node polls for new Leads on a 1-minute cadence, giving you near-real-time enrichment without building webhooks. The visual canvas makes it easy to see the trigger → filter → enrich → update flow, and self-hosted n8n means unlimited executions at zero platform cost.
The trade-off is OAuth setup. You need a Salesforce Connected App with the right scopes, which involves admin-level Salesforce configuration. For one-time backfill or ad-hoc enrichment without OAuth, the Claude Code approach is simpler.
How it works
- Salesforce Trigger node polls for new Lead or Contact records every 1 minute via OAuth
- IF node filters out records that already have enrichment data (Title, Phone populated) to avoid wasting Apollo credits
- HTTP Request node calls Apollo's People Enrich API with the record's email address to retrieve title, phone, department, LinkedIn URL, and company data
- Code node maps Apollo's response fields to Salesforce field names (e.g.,
person.title→Title,person.linkedin_url→LinkedIn_URL__c) - Salesforce node updates the Lead/Contact record with enriched fields, and optionally updates the parent Account with organization data
Step 1: Create a Salesforce Connected App
In Salesforce, go to Setup → App Manager → New Connected App:
- App Name: n8n Enrichment
- Enable OAuth Settings: checked
- Callback URL: your n8n OAuth callback URL
- OAuth Scopes:
Access and manage your data (api),Perform requests at any time (refresh_token, offline_access) - Save and note the Consumer Key and Consumer Secret
In n8n, add a Salesforce credential using these values.
Step 2: Add a Salesforce Trigger
Create a new workflow and add a Salesforce Trigger node:
- Authentication: Select your Salesforce credential
- Event: Record Created
- Object: Lead (or Contact, depending on your data model)
- Polling interval: 1 minute
n8n's Salesforce trigger polls for new records rather than using webhooks. Set the polling interval to 1 minute for near-real-time enrichment. For batch enrichment of existing records, use a Schedule Trigger instead.
Step 3: Filter already-enriched records
Add an IF node to skip records that already have enrichment data:
- Condition:
{{ $json.Title }}is empty OR{{ $json.Phone }}is empty
This prevents wasting Apollo credits on records that are already enriched from another source.
Step 4: Call Apollo People Enrich
Add an HTTP Request node:
- Method: POST
- URL:
https://api.apollo.io/v1/people/enrich - Headers:
x-api-key: your Apollo API keyContent-Type:application/json
- Body (JSON):
{
"email": "{{ $json.Email }}",
"reveal_personal_emails": false
}Apollo's People Search endpoint returns demographic data but not email addresses or phone numbers. Always use People Enrich with an email as input. For finding contacts at a company without emails, use People Search first, then People Enrich on each result.
Step 5: Check if Apollo returned data
Add an IF node:
- Condition:
{{ $json.person }}exists (is not null)
If Apollo didn't find a match, the workflow skips the update step.
Step 6: Map Apollo fields to Salesforce
Add a Set node to map the response:
| Apollo response field | Salesforce field | Notes |
|---|---|---|
person.title | Title | Job title |
person.linkedin_url | LinkedIn_URL__c | Custom field — create in Salesforce first |
person.phone_numbers[0].sanitized_number | Phone | First phone number |
person.organization.name | Company | For Leads only |
person.organization.estimated_num_employees | NumberOfEmployees | On the Account object |
person.organization.industry | Industry | On the Account object |
person.seniority | Seniority__c | Custom field (optional) |
person.departments[0] | Department | Standard field |
Configure the Set node to extract these fields:
// In a Code node for more control:
const person = $json.person;
if (!person) return [];
return [{
json: {
Title: person.title || '',
Phone: person.phone_numbers?.[0]?.sanitized_number || '',
LinkedIn_URL__c: person.linkedin_url || '',
Department: person.departments?.[0] || '',
// For Leads:
Company: person.organization?.name || '',
// Store these to update the Account separately:
org_industry: person.organization?.industry || '',
org_employees: person.organization?.estimated_num_employees || 0,
}
}];Step 7: Update the Salesforce record
Add a Salesforce node:
- Operation: Update
- Object: Lead (or Contact)
- Record ID:
{{ $('Salesforce Trigger').item.json.Id }} - Fields to update: Map from the Set node output (Title, Phone, LinkedIn_URL__c, Department, Company)
Step 8: Update the Account (optional)
If you want to enrich the parent Account with organization data, add a second Salesforce node:
- Operation: Update
- Object: Account
- Record ID: The Account ID from the Lead/Contact
- Fields: Industry, NumberOfEmployees
Step 9: Activate
- Click Execute Workflow to test with a real Lead
- Verify the Salesforce record is updated with Apollo data
- Toggle the workflow to Active
Rate limits and credits
- Apollo rate limit: ~100 enrichment requests per minute on Professional plan
- Credit cost: 1 credit per successful enrichment (waterfall enrichment costs additional credits)
- Bulk alternative: For enriching many existing records, use
/v1/people/bulk_enrich(up to 10 per call) with a Schedule Trigger + batch loop
Add error handling to the HTTP Request node for 429 (rate limit) and 402 (credits exhausted) responses. Log errors to a separate Slack channel so you know when to top up credits.
Troubleshooting
Common questions
How quickly does enrichment happen after a Lead is created?
n8n's Salesforce Trigger polls every 1 minute on the Starter plan (every 5 minutes on the Community plan). A new Lead created at 2:00 PM will typically be enriched by 2:01-2:02 PM. For true real-time (under 10 seconds), you'd need a Salesforce webhook pushing to n8n, which requires additional Salesforce configuration.
Should I use Apollo's native Salesforce integration instead?
Apollo's native integration syncs bi-directionally every 30 minutes with automatic deduplication. Use it for broad, always-on sync. Use this n8n approach when you need filtering (only enrich ICP-matching contacts), custom field mapping, or chaining with other tools like Slack notifications.
Can I enrich existing records, not just new ones?
Yes. Replace the Salesforce Trigger with a Schedule Trigger and add an HTTP Request node to run a SOQL query for records missing enrichment fields. Process the results through the same Apollo enrichment and update steps.
Cost
- n8n Cloud Starter: $24/mo for 2,500 executions. Each new Lead = 1 execution.
- Self-hosted: Free. Unlimited executions.
- Apollo: Credits vary by plan. Professional plan starts at $79/mo with increased API access.
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.