Module 6: CRM with HubSpot
Mini-Project: The Lead → Contact → Deal Flow
Capsule description
Time to bring the module together in the workflow this guide's strategy points to as M06's project: the lead → contact → deal flow.
Why is this flow the project of the module? Because it's the heart of any sales automation. A raw opportunity arrives — a filled form, an email from someone interested, a row in a sheet — and it has to become something actionable inside the CRM: a contact (the person), associated to a company, with a deal opened in the pipeline, a note explaining where it came from, and a follow-up task for the salesperson. All of that, without anyone capturing it by hand and without duplicating anything.
When you finish, you'll have the skeleton every SMB needs: end-to-end automated lead capture. It uses the whole module — contacts, deals, pipelines, companies, associations, activities, tasks — chained in the pattern you practiced in capsule 05.
What you'll build
A workflow called Lead Capture that, on receiving a raw lead:
- Creates or updates the company (without duplicating)
- Creates or updates the contact (without duplicating)
- Creates a deal in the first stage of the pipeline
- Associates the deal with the contact and with the company
- Logs a note explaining where the lead came from
- Creates a follow-up task for the salesperson
What you'll practice
- ✅ Create or Update for contacts and companies — deduplication (capsule 03, 05)
- ✅ Create a deal in the right pipeline stage (capsule 04)
- ✅ Associations chained with IDs (capsule 05)
- ✅ Log activity + create task — visible automation (capsule 06)
- ✅ The full "from the name to the ID to the association" pattern (capsule 05)
- ✅ Integrate HubSpot's complete model into a single flow
The guiding principle: idempotent end to end
Before building, lock in the quality goal. This workflow can receive the same lead several times — the user double-submits, the form retries, you test the workflow. The correct result always must be:
- One company (not one per run)
- One contact (not one per run)
- One deal per real opportunity
You already have the tools to achieve it: Create or Update for contact and company (HubSpot deduplicates by email and domain — capsule 03, 07), and normalized keys (email and domain in lowercase, no spaces). The deal is the only object that could duplicate if you're not careful — we handle it in the corresponding step.
Setup
- Your HubSpot account with the free CRM and the private app connected (capsule 02)
- Look at your default pipeline and note which is its first stage
- Be clear on which HubSpot user will be the owner of the follow-up tasks
Workflow architecture
Manual Trigger (or Webhook in production)
│
▼
Set: normalize the raw lead (email and domain in lowercase)
│
▼
HubSpot (Company: Create or Update) → Company ID
│
▼
HubSpot (Contact: Create or Update) → Contact ID
│
▼
HubSpot (Deal: Create, first stage) → Deal ID
│
▼
HubSpot (Associate: Deal ↔ Contact)
HubSpot (Associate: Deal ↔ Company)
HubSpot (Associate: Contact ↔ Company)
│
▼
HubSpot (log Note, associated to the contact)
│
▼
HubSpot (create follow-up Task, associated to the contact)
Step 1: the trigger and the raw lead
Manual Trigger → Set that simulates the incoming lead:
name = Jane Smith
email = JANE@acme.com
company = Acme Corp
domain = acme.com
phone = 555-1234
interest = Annual license plan
source = Web form
In production, this Set is replaced by a Webhook Trigger connected to your form. The rest of the workflow doesn't change.
Step 2: normalize the lead
Extend the Set (or add another) — defensive design is critical here because deduplication depends on the normalization:
name = {{ $json.name.trim() }}
email = {{ $json.email.toLowerCase().trim() }}
company = {{ $json.company.trim() }}
domain = {{ $json.domain.toLowerCase().trim() }}
phone = {{ $json.phone || '' }}
interest = {{ $json.interest || 'Not specified' }}
source = {{ $json.source || 'Unknown' }}
date = {{ $now.toFormat('yyyy-LL-dd HH:mm') }}
Email and domain in lowercase and without spaces — without this, HubSpot's deduplication (capsule 07) fails and duplicates slip in.
Step 3: create or update the company
A HubSpot node:
- Resource:
Company - Operation:
Create or Update - Deduplication key: the domain
- Properties:
name→{{ $json.company }},domain→{{ $json.domain }}
Rename it to Company. Its output gives you the Company ID.
Step 4: create or update the contact
A HubSpot node:
- Resource:
Contact - Operation:
Create or Update - Deduplication key: the email
- Properties:
email,firstname/lastname(split thenameif needed),phone→{{ $('Set').item.json.phone }},lifecyclestage→lead
Rename it to Contact. Its output gives you the Contact ID.
Step 5: create the deal
A HubSpot node:
- Resource:
Deal - Operation:
Create dealname→ something descriptive:{{ $('Set').item.json.company }} — {{ $('Set').item.json.interest }}pipeline→ your default pipeline (use the node's selector)dealstage→ the first stage (use the selector — remember capsule 07: name ≠ ID)
Rename it to Deal. Its output gives you the Deal ID.
On the deal's idempotency: unlike contact and company, the deal doesn't have a natural "key" HubSpot deduplicates. For this project, with a controlled Manual Trigger, we leave it as a simple
Create. In production (see below) you'd add a "look up before creating" for the deal — but don't complicate the skeleton now.
Step 6: the associations
Three association HubSpot nodes, chained — here everything from capsule 05 is used. Each one links two records by their IDs:
- Deal ↔ Contact: Deal ID
{{ $('Deal').item.json.id }}←→ Contact ID{{ $('Contact').item.json.id }} - Deal ↔ Company: Deal ID ←→ Company ID
{{ $('Company').item.json.id }} - Contact ↔ Company: Contact ID ←→ Company ID
After this, the deal in HubSpot shows its buyer and its organization; the contact shows its company. The CRM is a network, not three lists (capsule 05).
Step 7: log the note
A HubSpot node that logs a note associated to the contact ({{ $('Contact').item.json.id }}):
Lead captured automatically on {{ $('Set').item.json.date }}.
Source: {{ $('Set').item.json.source }}.
Interest: {{ $('Set').item.json.interest }}.
Deal created and associated.
This is capsule 06's visible automation: any salesperson who opens the contact sees that it came from an automation, from where, and what was done. The workflow doesn't work "in secret".
Step 8: create the follow-up task
A HubSpot node that creates a task associated to the contact:
- Title:
First contact: {{ $('Set').item.json.name }} ({{ $('Set').item.json.company }}) - Due date:
{{ $now.plus({ days: 1 }).toISO() }}— tomorrow - Owner: the HubSpot user of the salesperson on duty
- Status: pending
The salesperson opens HubSpot and already has their task. The workflow prepared; the human executes (capsule 06).
Step 9: test the workflow
Test 1: new lead
- Run the workflow with Jane Smith's lead
- Verify in HubSpot:
- The company
Acme Corpexists, the contactJane Smith, a deal in the first stage - The deal shows Jane and Acme associated; the contact shows Acme
- The contact has the origin note and the follow-up task
- The company
Test 2: the same lead again (the key test)
- Run again, without changing anything
- Verify: there's still a single company and a single contact —
Create or Updateupdated them, it didn't duplicate them - (The deal does duplicate with this skeleton — it's expected; see "to production")
Test 3: lead with a "dirty" email
- Change the email in the Set to
Jane@ACME.com(spaces and uppercase) - Run
- Verify: a new contact was not created — step 2's normalization made the deduplication work
Success checklist
- The lead creates a company, contact, and deal
- The deal ends up associated to the contact and company; the contact to the company
- The contact has an origin note — the automation is visible
- A follow-up task was created with an owner and a date
- Re-running doesn't duplicate the contact or company (
Create or Update) - A "dirty" email doesn't duplicate either (normalization)
- The workflow integrates HubSpot's complete model
How to take this to production
What you built is a reusable skeleton. For production:
- Webhook instead of Manual Trigger — connected to your real capture form.
- Idempotent deal: add a "look up before creating" for the deal — search whether there's already an open deal associated to that contact for that opportunity; if there is, don't create another. It's Module 1's pattern, applied to the deal.
- Route to the right salesperson: based on the source, the amount, or the region, assign the task (and the deal) to a different salesperson.
- Notify via Slack: add a message to the sales channel when a lead comes in — integrates Module 4. "A lead came in, assigned to @salesperson."
- Handle the lead with no email: an IF at the start — if there's no email, there's no deduplication key; decide what to do (reject, send to manual review).
- Handle errors: if HubSpot fails midway through the flow, you're left with a partial state (company yes, deal no). G10 (Production) covers how to make this resilient.
Module summary
With this project you close Module 6 — HubSpot CRM. Recapping what you now master:
- Connecting HubSpot with a private app and scopes (capsule 02)
- Contacts — the central object, deduplicated by email (capsule 03)
- Deals and pipelines — the opportunities and their advance through stages (capsule 04)
- Companies and associations — the CRM as a relational network (capsule 05)
- Activities and tasks — the follow-up that moves the sale (capsule 06)
- Diagnosing properties, IDs, and duplicates (capsule 07)
- The lead → contact → deal flow — the heart of sales automation (this capsule)
The module's star pattern: a CRM is a relational database with the sales process already modeled — automating it is chaining "look up before acting" to build its network of objects, without duplicating.
What comes next (Module 7):
You captured the lead and opened the deal. Module 7 — Calendly and scheduling closes the commercial cycle: when that lead books a meeting, a workflow receives it, confirms, and triggers the follow-up. You're going to connect scheduling with everything you've already built.
Additional resources
- n8n HubSpot node Docs - All the operations used in the project.
- n8n Webhook Trigger Docs - To take the project to production.
- HubSpot: the sales funnel - How the lead → customer lifecycle is modeled.
Created: May 14, 2026 Version: 1.0