AutoFlowLab
← Tutorials

Automate Lead Capture With AI: Webform to CRM in n8n

Build an n8n workflow that captures website leads, scores them with an LLM, writes them to HubSpot, and pings Slack for hot leads. Zapier variant included.

April 14, 2026 · beginner · 45 minutes setup

Every lead that sits in an inbox overnight is a lead you’re statistically losing. The classic stat — conversion rates drop sharply after the first five minutes — holds up in practice: by the time someone manually copies a form submission into the CRM, tags it, and pings sales, the prospect has already filled out a competitor’s form.

This tutorial builds a workflow that fixes that. A website form fires a webhook, an LLM scores and summarizes the lead in a few seconds, the lead lands in HubSpot with the score attached, and anything hot gets pushed straight into a Slack channel where a human can jump on it. Total setup time is about 45 minutes, and the primary build is in n8n with a shorter Zapier version at the end.

What you’re building

The flow has four moving parts:

  1. Webhook node — receives the form submission (works with Webflow, Framer, Tally, WordPress forms, or a plain HTML form).
  2. OpenAI node — scores the lead 1–100 and writes a two-sentence summary, returning strict JSON.
  3. IF node — splits the flow at a score threshold (we’ll use 70).
  4. HubSpot + Slack nodes — every lead goes to HubSpot; hot leads also go to Slack.

You’ll need: an n8n instance (cloud or self-hosted), an OpenAI API key (or any chat-completion-compatible provider), a HubSpot account with a private app token, and a Slack workspace where you can create an app.

If you want to skip the manual build, grab the template and import it via Workflow → Import from File:

Free template · n8n

AI Lead Qualifier & CRM Router

ai-lead-qualifier-n8n.json

Download JSON

Step 1: Set up the Webhook trigger

  1. Create a new workflow and add a Webhook node.
  2. Set HTTP Method to POST and Path to something non-guessable like lead-intake-7f3a.
  3. Set Respond to Immediately — form providers time out fast, and you don’t want the form to hang while the LLM thinks. The AI step runs after the response is sent.
  4. Copy the Test URL and point your form’s webhook/integration setting at it. Submit a test entry so n8n captures a sample payload.

Most form tools send JSON like this:

{
  "name": "Dana Reyes",
  "email": "dana@acmelogistics.com",
  "company": "Acme Logistics",
  "message": "We need to automate dispatch notifications for ~40 drivers. What does onboarding look like?"
}

If your form nests fields under body.data or similar, add a Set node after the webhook to flatten them into name, email, company, and message. Doing the flattening once here keeps every downstream expression clean.

Step 2: Score the lead with the OpenAI node

  1. Add an OpenAI node (the “Message a model” operation under Chat).
  2. Create your OpenAI credential with the API key.
  3. Pick a cheap, fast model — for classification and summarization tasks like this, the smallest current-generation model is plenty. As of mid-2026, check current pricing, but a lead qualification call like this typically costs a fraction of a cent.
  4. Enable Output Content as JSON (or set response_format to JSON mode if you’re using the HTTP Request node against the API directly).

Use this as the System message:

You are a lead qualification assistant for a B2B software company.
You receive a website form submission and return ONLY a JSON object,
no markdown, no commentary, with exactly these keys:

{
  "score": <integer 1-100>,
  "summary": "<two sentences max, plain text>",
  "reason": "<one short sentence explaining the score>"
}

Scoring rubric:
- 80-100: clear buying intent, mentions team size, budget, timeline, or a concrete use case
- 50-79: relevant inquiry but vague on intent or scale
- 20-49: generic question, student/job seeker, or unclear fit
- 1-19: spam, gibberish, or solicitation

A business email domain adds credibility; free email domains
(gmail, outlook, yahoo) should not be auto-penalized but score
lower when combined with a vague message. Never return any key
other than score, summary, reason.

And this as the User message, using n8n expressions:

Name: {{ $json.name }}
Email: {{ $json.email }}
Company: {{ $json.company }}
Message: {{ $json.message }}

Run the node with your test data. You should get back a clean object like {"score": 84, "summary": "...", "reason": "..."}.

Add a parsing safety net

LLMs occasionally wrap JSON in code fences despite instructions. Add a Code node right after the OpenAI node:

const raw = $input.first().json.message?.content ?? $input.first().json.content;
const cleaned = String(raw).replace(/```json|```/g, '').trim();
let parsed;
try {
  parsed = JSON.parse(cleaned);
} catch (e) {
  parsed = { score: 50, summary: 'Parse failed — review manually', reason: 'LLM returned malformed JSON' };
}
return [{ json: { ...$('Webhook').first().json, ...parsed } }];

This merges the original form fields with the AI output and guarantees the workflow never dies on a malformed response — a failed parse just becomes a neutral score-50 lead for manual review.

Step 3: Route with an IF node

  1. Add an IF node.
  2. Condition: Number{{ $json.score }}larger or equal70.

The true branch is your hot path (HubSpot + Slack), the false branch is the standard path (HubSpot only). Start at 70 and tune after a week of real traffic — if sales complains about noise, raise it; if good leads sit untouched, lower it.

Step 4: Write every lead to HubSpot

On both branches (or merge first with a Merge node and put HubSpot after it):

  1. Add a HubSpot node, resource Contact, operation Create or Update.
  2. Authenticate with a Private App token (Settings → Integrations → Private Apps in HubSpot). Grant the scopes crm.objects.contacts.read and crm.objects.contacts.write — missing the write scope is the single most common setup failure here.
  3. Map fields:
    • Email: {{ $json.email }}
    • First Name / Last Name: split from {{ $json.name }}
    • Company Name: {{ $json.company }}
  4. Create two custom contact properties in HubSpot first (ai_lead_score, number; ai_lead_summary, multi-line text), then map {{ $json.score }} and {{ $json.summary }} to them under Additional Fields → Custom Properties.

Using “Create or Update” keyed on email means repeat submissions update the existing contact instead of throwing duplicate errors.

Step 5: Ping Slack for hot leads

On the true branch only:

  1. Add a Slack node, operation Send Message, and connect via OAuth2. The Slack app needs the chat:write bot scope, and the bot must be invited to the target channel (/invite @yourbot).
  2. Channel: #hot-leads.
  3. Message text:
🔥 Hot lead ({{ $json.score }}/100): {{ $json.name }} — {{ $json.company }}
{{ $json.summary }}
Why: {{ $json.reason }}
Email: {{ $json.email }}

Activate the workflow, switch your form to the Production URL of the webhook, and submit a real test. You should see the contact in HubSpot within seconds and — if your test message mentions budget and team size — a Slack ping.

Try it yourself

n8n

One n8n instance runs this entire flow with no per-task fees — generous for high-volume lead forms.

Start with n8n

The same flow in Zapier

If your team already lives in Zapier and your lead volume is modest, the equivalent Zap is five steps:

  1. Trigger: Webhooks by Zapier → Catch Hook (or a native trigger like Typeform/Tally if your form has one — prefer native, it handles field mapping for you).
  2. Action: OpenAI → Conversation. Paste the same system and user prompts from Step 2. Zapier’s OpenAI integration doesn’t enforce JSON mode, so keep the “return ONLY a JSON object” instruction prominent.
  3. Action: Formatter by Zapier → Utilities → if you need to extract fields, or use Code by Zapier with a JSON.parse wrapped in try/catch, mirroring the safety net above.
  4. Action: HubSpot → Create or Update Contact, mapping score and summary to the same custom properties.
  5. Action: Filter by Zapier (score ≥ 70) → Slack → Send Channel Message.

Note that the Filter sits before Slack only — Zapier filters halt everything after them, so order matters: HubSpot must come before the filter, Slack after.

The trade-off: each lead burns 4–5 Zapier tasks. At a few hundred leads a month that’s fine; at thousands, the math favors n8n quickly (as of mid-2026, check current pricing on both).

When to pick Zapier vs Make vs n8n for lead capture

  • Zapier wins when your form tool has a native trigger, your volume is under ~500 leads/month, and nobody on the team wants to look at JSON. Fastest to ship, most expensive per lead.
  • Make is the middle ground — its visual router handles the hot/cold split elegantly and operations pricing is gentler than Zapier’s task pricing. Pick it if you’re already a Make shop.
  • n8n wins on volume economics, the Code-node safety net (genuinely hard to replicate in Zapier without awkward workarounds), and self-hosting if lead data can’t leave your infrastructure. The cost is hosting and a slightly steeper learning curve.

Full breakdown in our Make vs Zapier vs n8n comparison.

Common errors and fixes

HubSpot returns 403 Forbidden. Your private app token is missing crm.objects.contacts.write. Edit the private app’s scopes, save, and re-create the credential in n8n — tokens don’t pick up scope changes retroactively in every case, so regenerate if it still fails.

Slack message silently never arrives. Ninety percent of the time the bot isn’t in the channel. Run /invite @yourbot in #hot-leads. The API error (not_in_channel) is visible in the node’s error output if you check executions.

Webhook receives nothing. You’re still pointing the form at the Test URL, which only listens while you’re in “Listen for test event” mode. Switch the form to the Production URL and make sure the workflow toggle is set to Active.

LLM returns markdown-fenced JSON or chatty preamble. The Code-node safety net in Step 2 catches this, but you can reduce the frequency by using the model’s native JSON/structured-output mode and keeping temperature at 0.

OpenAI 429 rate limit during a traffic spike. New API accounts have low requests-per-minute tiers. Enable Retry on Fail on the OpenAI node (3 tries, 5000ms wait) and the spike smooths itself out.

Duplicate contacts in HubSpot. You used “Create” instead of “Create or Update.” Switch the operation — email is the dedupe key.

Tune it after launch

Run the workflow for two weeks, then pull the scores from HubSpot and compare against what sales actually closed or qualified. The rubric in the system prompt is where tuning happens — add domain-specific signals (“mentions of Shopify or WooCommerce indicate strong fit”) and concrete disqualifiers (“agency outreach offering services scores under 20”). The score threshold and the rubric are the two levers; the plumbing shouldn’t need touching again.

When you’re ready to extend the pattern, the same webhook-LLM-route skeleton powers our AI customer support bot — same architecture, different prompt.

Try it yourself

Zapier

Prefer zero-maintenance hosting? Zapier runs this flow in five steps with native form triggers.

Start with Zapier