In this tutorial we'll build a real, production-grade lead routing workflow in n8n. When a form is submitted, the workflow enriches the lead, uses an LLM to score and summarize it, routes hot leads to a rep in Slack, and writes everything back to your CRM — with proper error handling. No fluff, just the build.

What you'll need
An n8n instance (cloud or self-hosted), an OpenAI-compatible API key, a Slack workspace, and a CRM with an API (HubSpot, Pipedrive, or Airtable all work).

The workflow at a glance

  1. Webhook receives the form submission.
  2. Enrich the email domain with company data.
  3. AI score & summarize the lead with an LLM.
  4. Route hot leads to Slack, nurture the rest.
  5. Persist the full record to the CRM.
  6. Catch any failure and alert the team.
Dashboard of connected automation steps
The finished workflow: trigger → enrich → score → route → persist, with a global error branch.

Step 1 — Capture the lead with a Webhook node

Add a Webhook node, set the method to POST, and point your form at the production URL. n8n gives you a test URL while building — use it to send a sample payload like the one below.

sample-payload.json
{
  "name": "Dana Lee",
  "email": "[email protected]",
  "company": "Acme Robotics",
  "message": "We need to automate our support inbox. ~5k tickets/mo.",
  "budget": "$5k-10k/mo"
}

Step 2 — Enrich the lead

Add an HTTP Request node to look up the company by email domain. Always set a timeout and let the workflow continue on failure so a flaky enrichment API never blocks a lead.

Set 'Continue On Fail'
In the node's Settings tab, enable Continue On Fail. Enrichment is a nice-to-have — a missing company record should never stop a lead from being routed.

Step 3 — Score and summarize with an LLM

Add an OpenAI (or HTTP Request) node. We ask the model to return strict JSON so the next nodes can branch on it deterministically. Notice the explicit schema and the instruction to only return JSON.

scoring-prompt.js
// System prompt for the scoring node
const system = `You are a B2B SDR. Score this lead 0-100 on fit and intent.
Return ONLY valid JSON, no prose, matching:
{ "score": number, "tier": "hot" | "warm" | "cold", "summary": string }`;

// User content built from previous nodes
const user = JSON.stringify({
  name: $json.name,
  email: $json.email,
  company: $json.company,
  message: $json.message,
  budget: $json.budget,
  enrichment: $node["Enrich"].json ?? null
});

return { system, user };
parse-score.js
// Function node: safely parse the model output
let parsed;
try {
  parsed = JSON.parse($json.choices[0].message.content);
} catch (e) {
  // Fallback keeps the workflow alive if the model returns junk
  parsed = { score: 50, tier: "warm", summary: "Unparsed model output" };
}

return [{ json: { ...$json, ...parsed } }];
Never trust raw model output
LLMs occasionally return malformed JSON or extra prose. The try/catch fallback above is the difference between a workflow that degrades gracefully and one that crashes at 2am.

Step 4 — Route based on tier

Add a Switch node keyed on {{ $json.tier }}. Send hot leads to a Slack node that @-mentions the on-call rep with the AI summary, and route warm/cold leads into your nurture sequence.

slack-message.txt
:fire: *New HOT lead* — score {{ $json.score }}/100
*{{ $json.name }}* @ {{ $json.company }}
{{ $json.summary }}
<mailto:{{ $json.email }}|Reply now>

Step 5 — Persist to your CRM

Finally, write the enriched, scored record to your CRM with an upsert keyed on email so re-submissions update the existing contact instead of creating duplicates.

CRM fieldMaps fromNotes
email{{ $json.email }}Upsert key
company{{ $json.company }}From form or enrichment
lead_score{{ $json.score }}0–100
lead_tier{{ $json.tier }}hot / warm / cold
ai_summary{{ $json.summary }}Shown to rep
Field mapping for the CRM upsert node.

Step 6 — Add a global error workflow

Don't ship without these

  • An Error Trigger workflow that posts failures to Slack
  • Timeouts on every HTTP/LLM node
  • 'Continue On Fail' on non-critical nodes
  • A test execution saved with sample data
  • Credentials stored in n8n, never hard-coded
Impact of automating lead routing
<60s
lead to rep
was ~4 hours
100%
leads enriched & scored
0
leads lost to manual triage

Frequently asked questions