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:

  1. Creates or updates the company (without duplicating)
  2. Creates or updates the contact (without duplicating)
  3. Creates a deal in the first stage of the pipeline
  4. Associates the deal with the contact and with the company
  5. Logs a note explaining where the lead came from
  6. 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 the name if needed), phone{{ $('Set').item.json.phone }}, lifecyclestagelead

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:

  1. Deal ↔ Contact: Deal ID {{ $('Deal').item.json.id }} ←→ Contact ID {{ $('Contact').item.json.id }}
  2. Deal ↔ Company: Deal ID ←→ Company ID {{ $('Company').item.json.id }}
  3. 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

  1. Run the workflow with Jane Smith's lead
  2. Verify in HubSpot:
    • The company Acme Corp exists, the contact Jane 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

Test 2: the same lead again (the key test)

  1. Run again, without changing anything
  2. Verify: there's still a single company and a single contact — Create or Update updated them, it didn't duplicate them
  3. (The deal does duplicate with this skeleton — it's expected; see "to production")

Test 3: lead with a "dirty" email

  1. Change the email in the Set to Jane@ACME.com (spaces and uppercase)
  2. Run
  3. 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

  1. n8n HubSpot node Docs - All the operations used in the project.
  2. n8n Webhook Trigger Docs - To take the project to production.
  3. HubSpot: the sales funnel - How the lead → customer lifecycle is modeled.

Created: May 14, 2026 Version: 1.0