Enrich new Salesforce contacts with Apollo using n8n

medium complexityCost: $0-24/moRecommended

Prerequisites

Prerequisites
  • 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.titleTitle, person.linkedin_urlLinkedIn_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:

  1. App Name: n8n Enrichment
  2. Enable OAuth Settings: checked
  3. Callback URL: your n8n OAuth callback URL
  4. OAuth Scopes: Access and manage your data (api), Perform requests at any time (refresh_token, offline_access)
  5. 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
Polling, not webhooks

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 key
    • Content-Type: application/json
  • Body (JSON):
{
  "email": "{{ $json.Email }}",
  "reveal_personal_emails": false
}
People Search does NOT return emails

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 fieldSalesforce fieldNotes
person.titleTitleJob title
person.linkedin_urlLinkedIn_URL__cCustom field — create in Salesforce first
person.phone_numbers[0].sanitized_numberPhoneFirst phone number
person.organization.nameCompanyFor Leads only
person.organization.estimated_num_employeesNumberOfEmployeesOn the Account object
person.organization.industryIndustryOn the Account object
person.senioritySeniority__cCustom field (optional)
person.departments[0]DepartmentStandard 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

  1. Click Execute Workflow to test with a real Lead
  2. Verify the Salesforce record is updated with Apollo data
  3. 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
Monitor credit consumption

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.