n8nb2benrichment

B2B Enrichment API Workflow in n8n

n8n lead enrichment chaining Apollo, Clearbit, and verification services breaks when schemas change. Normalizer pattern with SERP fallback for resilience.

9 min

B2B lead enrichment in n8n typically chains multiple HTTP Request nodes calling different APIs: Apollo for contacts, Clearbit for firmographics, and a verification service for email validation. The result is a fragile pipeline where one API change breaks the entire workflow. A normalizer pattern with structured search as a fallback makes the pipeline resilient.

The Standard n8n Enrichment Stack

Most n8n lead enrichment workflows follow the same pattern: webhook receives a lead (company URL or name), HTTP Request node calls an enrichment API, IF node checks for data, fallback node calls a second API if the first returned nothing. The problem is that each enrichment API returns different JSON shapes, and field names change without warning.

Apollo returns contacts in a "people" array. Prospeo returns a flat object with "email" and "company" fields. Clearbit returns nested objects with "company.name" and "person.email" paths. Downstream nodes written for one provider's schema break when you switch to another or when the provider updates their response format.

SERP-Based Enrichment as a Fallback

When primary enrichment APIs return no data (common for small companies or newer startups), a Google SERP search for the company name returns signals that fill the gaps: company website from the first organic result, LinkedIn page from organic results, review data from Google Business profile, and industry context from snippets.

Python
import os, requests

API_KEY = os.environ["SCAVIO_API_KEY"]
H = {"x-api-key": API_KEY, "Content-Type": "application/json"}

def enrich_from_serp(company_name, domain=None):
    query = f"{company_name} company" if not domain else f"site:{domain} about"
    res = requests.post("https://api.scavio.dev/api/v1/search",
        headers=H, json={"query": query, "country_code": "us"})
    data = res.json()
    organics = data.get("organic_results", [])

    enriched = {
        "company_name": company_name,
        "website": None,
        "linkedin": None,
        "description": None,
        "industry_signals": [],
    }

    for r in organics[:10]:
        link = r.get("link", "")
        if "linkedin.com/company" in link and not enriched["linkedin"]:
            enriched["linkedin"] = link
        elif not enriched["website"] and company_name.lower() in r.get("title", "").lower():
            enriched["website"] = link
            enriched["description"] = r.get("snippet", "")[:200]
        enriched["industry_signals"].append(r.get("snippet", "")[:100])

    knowledge = data.get("knowledge_graph", {})
    if knowledge:
        enriched["description"] = knowledge.get("description", enriched["description"])
        enriched["website"] = knowledge.get("website", enriched["website"])

    return enriched

result = enrich_from_serp("Scavio")
print(f"Website: {result['website']}")
print(f"LinkedIn: {result['linkedin']}")
print(f"Description: {result['description']}")

n8n Workflow Pattern

The resilient pattern: Primary enrichment (Apollo/Prospeo) feeds into a normalizer Function node. If the normalizer's _has_data field is false, the workflow routes to a SERP-based fallback enrichment node. Both paths merge through a second normalizer that outputs the same schema regardless of which provider supplied the data.

JavaScript
// n8n Function node: SERP Enrichment Normalizer
// Normalizes Scavio search results into the standard enrichment schema

const input = $input.first().json;
const organics = input.organic_results || [];

const normalized = {
  _source: "scavio_serp",
  _enriched_at: new Date().toISOString(),
  _has_data: organics.length > 0,
  company_name: null,
  domain: null,
  industry: null,
  employee_count: null,
  email: null,
  phone: null,
  linkedin_url: null,
  error: null,
};

for (const r of organics.slice(0, 10)) {
  const link = r.link || "";
  if (link.includes("linkedin.com/company") && !normalized.linkedin_url) {
    normalized.linkedin_url = link;
  }
  if (!normalized.domain && r.title && !link.includes("linkedin") && !link.includes("yelp")) {
    normalized.domain = link.split("/")[2] || null;
    normalized.company_name = r.title;
  }
}

const kg = input.knowledge_graph || {};
if (kg.description) normalized.industry = kg.description.slice(0, 100);
if (kg.website) normalized.domain = kg.website;

return [{ json: normalized }];

Cost Comparison

Apollo Professional: $99/mo for 24,000 credits (roughly 800 enrichment lookups at 30 credits each). Prospeo: $99/mo for 5,000 email lookups. SERP-based enrichment: $0.005/query, unlimited.

For a workflow enriching 200 leads/month: Apollo alone costs $99/mo. A waterfall of SERP search (primary) + Apollo (for contacts that SERP found companies for) might use 200 SERP queries ($1.00) + 150 Apollo lookups (within free tier). Total: $1.00/month for comparable results on company data, though email finding still requires a dedicated provider.

The honest tradeoff: SERP-based enrichment provides company context (website, LinkedIn, industry signals) but not direct contact information (email, phone). For complete lead enrichment, you still need Apollo, Prospeo, or similar for the contact layer. SERP enrichment is a complement, not a replacement.