Loading...

Lead Enrichment

Self-Hosted Lead Enrichment That Scores A Signup In Four Seconds

Take a raw form submission, resolve the company behind the email domain, score it against your ICP, and write a deduplicated record into the CRM.

n8n (Self-Hosted or Cloud)IntermediateHalf a day including the deployUpdated August 21, 2026
How the data moves
01WebhookForm posts straight into n8n
02NormaliseStrip aliases, reject free mail, resolve the domain
03EnrichApollo returns firmographics; cached in Supabase
04ScoreICP rules applied in a Function node
05UpsertCRM record written by domain, never duplicated

n8n · Apollo.io · Supabase · HubSpot · Docker

Download Workflow JSON: Import straight into any n8n instance, self-hosted or cloud.

Deploy to n8n Cloud: If you would rather not own the box. The workflow file is identical either way.

Copy docker-compose.yml: Postgres rather than the default SQLite. SQLite survives a demo and corrupts under concurrent executions.

The Problem

Signups arrive with an email address and nothing else. Someone opens LinkedIn, works out whether the company is worth a call, and types what they found into the CRM. By the time that happens the lead is a day old and the record is inconsistent with every other record.

Prerequisites & Tool Stack

Everything this blueprint calls. Check you have them before you start - the usual reason a build stalls halfway is a key that takes a day to get approved.

A VPS, or n8n Cloud

This runs comfortably on the smallest shared-vCPU box. Self-hosting is what makes the per-lead cost effectively zero.

Free tier: From about $4.50 a month

Apollo.io API key

Resolves a domain into headcount, industry, and funding. Any enrichment provider works - the node is isolated behind one HTTP call.

Free tier: Limited monthly credits

Supabase project

Caches enrichment by domain. The second signup from the same company costs nothing, which matters more than it sounds once you are paying per credit.

Free tier: Two projects, 500MB each

A CRM with an upsert endpoint

HubSpot in the file. Any CRM that can match on a unique property will do.

Some links above are partner links and we earn a commission if you sign up through them. It costs you nothing extra, and it does not decide what goes in a blueprint - the self-hosted option is recommended wherever it is genuinely the better call.

What It Costs To Run

Monthly, at the volumes this blueprint was tested against. Worth comparing against what the manual version of this work costs you in hours.

ComponentCostAt what volume
Hetzner CX22$4.59/moRuns n8n, Postgres, and Caddy with room left
Apollo credits~$0.03/leadCached by domain, so repeat companies are free
Supabase$0Free tier, a single cache table

The files

Get the workflow and the deploy files

The workflow imports in one paste. The compose file and Caddy config are the part that usually costs an afternoon, and both are included.

lead-enrichment.workflow.json - import straight into n8n

docker-compose.yml with Postgres, plus a hardened .env template

Caddyfile for TLS on a bare VPS

The Supabase cache table as SQL, with the domain index

The download link arrives by email and works for 30 days. We send notes on new blueprints, roughly monthly, and one click unsubscribes. See ourprivacy policy.

Technical Breakdown

The decisions that matter, in the order you will meet them. Everything here is a thing that broke in testing before it was a rule.

01.Take the webhook, answer immediately

Respond 202 the moment the payload validates, then do the work. Enrichment takes two to four seconds and the form in front of it should never wait on that. n8n does this with a Respond to Webhook node placed before the enrichment branch.

02.Reject what is not worth enriching

Free mail domains, role addresses, and anything failing a syntax check stop here and go to the CRM unenriched. Sending gmail.com to an enrichment API burns a credit to be told it is Google.

javascript
const FREE_MAIL = new Set(['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'icloud.com']);
const ROLE = /^(info|support|sales|admin|hello|contact|billing)@/i;

const email = $json.email.trim().toLowerCase();
const domain = email.split('@')[1] ?? '';

return [{
  json: {
    email,
    domain,
    enrichable: Boolean(domain) && !FREE_MAIL.has(domain) && !ROLE.test(email),
  },
}];

03.Check the cache before the API

Look the domain up in Supabase first. Enrichment data is stable for weeks, so a 30-day cache typically removes 40 to 60 percent of calls once the same companies start signing up twice.

04.Score in code, not in nodes

One Function node holding the ICP rules beats nine chained IF nodes. The scoring logic changes most often, and a single readable function is something you can diff and a colleague can review.

05.Upsert on domain, never insert

Match on company domain and update in place. Insert-only is how a CRM ends up with the same account four times under four spellings, and no enrichment pipeline survives that for long.

When It Goes Wrong

The difference between a demo and something you can leave running is entirely in this table. Every row is a failure the blueprint handles explicitly rather than hoping about.

FailureWhat happens
Apollo times outTwo retries, then the lead is written unenriched and flagged for a nightly backfill run.
Malformed webhook bodyRejected 400 at the validation node. The raw body is logged so a broken form is visible immediately.
CRM rate limitQueued in Postgres and retried with backoff. The webhook has already answered, so nothing upstream notices.
Duplicate submissionEmail plus a 60-second window checked against the cache table, so a double-click produces one record.

Payload Example

The record written to the CRM.

json
{
  "email": "dana@northwind-logistics.com",
  "domain": "northwind-logistics.com",
  "company": "Northwind Logistics",
  "headcount": 240,
  "industry": "Transportation & Logistics",
  "country": "US",
  "icp_score": 82,
  "tier": "A",
  "enriched_from": "cache",
  "enriched_at": "2026-08-21T14:02:11Z"
}
On this page

Download the files


Platform: n8n (Self-Hosted or Cloud)

Category: Lead Enrichment

Level: Intermediate — Comfortable with APIs, keys, and a JSON payload.

Build time: Half a day including the deploy

Not sure this is the right platform?

The same visual model, but the workflow is a JSON file you own and can run on your own box.

Browse All Blueprints

Done-For-You

Want this wired into your CRM without the weekend?

The workflow assumes a clean HubSpot. Most CRMs we meet have custom properties, a dedupe history, and a sales team with opinions about tiering.

A TechZapp sprint is a fixed-scope, fixed-price week. A senior engineer builds it in your environment, hands over the repository and the runbook, and you own every part of it afterwards. No platform of ours to keep paying for.

What the sprint covers

Deployed on your infrastructure with TLS, backups, and monitoring

ICP scoring built from your closed-won data rather than from a guess

Mapped to your CRM properties, with a dry-run against a copy before it writes

Sprint pricing

$1,500 - $2,000

One sprint, typically 5 working days


Fixed scope agreed before we start

Built in your environment, not ours

Repository, infrastructure, and runbook handed over

Two weeks of support after handover included

Scope This Sprint

Tell us what you are integrating with. We reply within one business day, and we will say plainly if the blueprint above already covers it.