How to Build a CRM Workflow with n8n

How to Build a CRM Workflow with n8n (Step-by-Step Guide)

I used to spend the first 90 minutes of every morning doing the same thing: copying form submissions into HubSpot, firing off welcome emails, pinging my co-founder on Slack, and updating a shared spreadsheet so our tiny sales team knew what happened overnight. It was mind-numbing, and I kept making mistakes — duplicate contacts, typos in names, emails that went out hours late.

Then I built an n8n CRM workflow that replaced every one of those steps. The entire pipeline — from the moment a lead fills out a form to the moment my team sees a Slack notification — now runs in under three seconds, with zero manual effort on my part.

In this tutorial I will walk you through exactly how I built it, node by node, so you can set up the same CRM automation in n8n for your own business.

Why Automate Your CRM?

If you are running a startup or a small sales team, manual CRM entry is one of the worst uses of your time. Here is what automation gives you:

Speed: Leads get a welcome email within seconds of submitting a form, not hours.
Accuracy: No more misspelled names or forgotten follow-ups.
Visibility: Every lead is logged in your spreadsheet and announced in Slack instantly.
Scalability: Whether you get 5 leads a day or 500, the workflow handles it the same way.

The best part about using n8n for this is that it is open-source, self-hostable, and connects to virtually every CRM and tool you already use. You own your data and your automation logic.

What We Will Build

By the end of this guide, you will have a fully working n8n CRM workflow that does the following:

1. Catches a form submission via a Webhook trigger
2. Validates the data with an IF node (rejects incomplete submissions)
3. Creates or updates a contact in HubSpot (or Google Contacts)
4. Sends a personalized welcome email to the new lead
5. Posts a notification in your team’s Slack channel
6. Logs the lead in a Google Sheets spreadsheet for reporting

Here is the high-level flow:

“`
Form Submission –> Webhook –> IF (validate) –> HubSpot Contact
|
+————+————+
| | |
Welcome Email Slack Msg Google Sheet
“`

Prerequisites

Before you start building, make sure you have the following ready:

An n8n account: You can self-host or use n8n Cloud (the cloud version is the fastest way to get started — no Docker or server config needed).
A HubSpot account: The free tier works perfectly for this workflow. You will need an API key or Private App token.
A Gmail or SMTP email account: For sending welcome emails.
A Slack workspace: With permission to add an incoming webhook or install the n8n Slack app.
A Google Sheet: Create a blank spreadsheet with columns: `Date`, `Name`, `Email`, `Company`, `Source`, `Status`.

If you are brand new to n8n, I recommend reading my n8n beginner guide first to get comfortable with the editor.

Step-by-Step: Building Your n8n CRM Workflow

Step 1: Set Up the Webhook Trigger

The Webhook node is the entry point of our workflow. It creates a unique URL that your form (Typeform, Webflow, a custom HTML form, or anything else) will POST data to.

1. Open your n8n editor and create a new workflow.
2. Click the + button and search for Webhook.
3. Add the Webhook node and configure it:
HTTP Method: `POST`
Path: `crm-lead-capture`
Authentication: None (or add Header Auth for production)
Response Mode: `Last Node` (so your form gets a confirmation back)

[SCREENSHOT: Webhook node configuration panel showing POST method and path]

4. Click Listen for Test Event, then send a test POST request using curl or your form:

“`bash
curl -X POST https://your-n8n-instance.com/webhook-test/crm-lead-capture \
-H “Content-Type: application/json” \
-d ‘{
“firstName”: “Maria”,
“lastName”: “Chen”,
“email”: “[email protected]”,
“company”: “Acme Corp”,
“source”: “landing-page”
}’
“`

You should see the test data appear in the Webhook node output. That confirms the trigger is working.

Step 2: Validate the Data with an IF Node

Not every submission will have complete data. I learned this the hard way when my workflow started creating contacts with blank email fields. The IF node prevents that.

1. Add an IF node after the Webhook.
2. Configure the condition:
Value 1: `{{ $json.email }}`
Operation: `is not empty`
3. Add a second condition (click Add Condition):
Value 1: `{{ $json.firstName }}`
Operation: `is not empty`
4. Set Combine Conditions to `AND` — both must be true.

[SCREENSHOT: IF node with two conditions checking email and firstName are not empty]

The true branch continues the workflow. The false branch can connect to a simple Respond to Webhook node that returns a 400 error, or you can just leave it unconnected for now.

For the false branch, I like to add a Set node that logs the rejected submission:

“`json
{
“status”: “rejected”,
“reason”: “Missing required fields”,
“receivedData”: “{{ $json }}”
}
“`

Step 3: Create or Update the Contact in HubSpot

This is the core of the n8n CRM workflow. We will use the HubSpot node to create a new contact, or update an existing one if the email already exists.

1. Add a HubSpot node after the true branch of the IF node.
2. Connect your HubSpot credentials (API Key or OAuth2).
3. Configure the node:
Resource: `Contact`
Operation: `Create or Update`
Email: `{{ $json.email }}`
Additional Fields:
– First Name: `{{ $json.firstName }}`
– Last Name: `{{ $json.lastName }}`
– Company: `{{ $json.company }}`
– Lead Status: `NEW`

[SCREENSHOT: HubSpot node configuration with Create or Update operation and mapped fields]

Using Google Contacts instead? If you do not use HubSpot, you can swap this node for a Google Contacts node. The setup is similar — map the name and email fields, connect your Google credentials, and set the operation to Create.

Pro tip: I also add a custom property in HubSpot called `lead_source` and map it to `{{ $json.source }}`. This lets me track which landing page or campaign generated each lead directly inside my CRM.

Step 4: Send a Welcome Email

Responding quickly to new leads matters. Studies consistently show that response time is one of the biggest factors in conversion. This node sends a personalized welcome email the instant someone submits the form.

1. Add a Send Email node (or Gmail node if you prefer OAuth).
2. Configure it:
To: `{{ $json.email }}`
Subject: `Welcome to [Your Company], {{ $json.firstName }}!`
Body (HTML):

“`html

Hey {{ $json.firstName }},

Thanks for reaching out! I wanted to personally welcome you
and let you know that someone from our team will be in touch
within 24 hours.

In the meantime, here are a few resources you might find helpful:

Looking forward to connecting!

Best,
Javier

“`

[SCREENSHOT: Email node configuration with HTML body and expression-based subject line]

If you want more sophisticated email workflows (drip sequences, conditional content), check out my guide on n8n email automation.

Step 5: Notify Your Team on Slack

Your sales team needs to know about new leads immediately. A Slack notification ensures nobody misses a hot lead.

1. Add a Slack node.
2. Connect your Slack credentials.
3. Configure it:
Resource: `Message`
Operation: `Send`
Channel: `#new-leads` (or whatever channel your team monitors)
Text:

“`
:rocket: New Lead Captured!

*Name:* {{ $json.firstName }} {{ $json.lastName }}
*Email:* {{ $json.email }}
*Company:* {{ $json.company }}
*Source:* {{ $json.source }}
*Time:* {{ $now.format(‘yyyy-MM-dd HH:mm’) }}
“`

[SCREENSHOT: Slack node configuration with formatted message text]

Important: Connect the Slack node to the HubSpot node’s output, not the IF node. This way the Slack message only fires after the contact is successfully created in your CRM.

Step 6: Log to Google Sheets

The final piece is a spreadsheet log. This gives your team a single, filterable view of all leads without needing HubSpot access.

1. Add a Google Sheets node.
2. Connect your Google credentials.
3. Configure it:
Operation: `Append Row`
Document: Select your spreadsheet
Sheet: `Sheet1`
Mapping Mode: `Map Each Column`
Columns:
– Date: `{{ $now.format(‘yyyy-MM-dd HH:mm:ss’) }}`
– Name: `{{ $json.firstName }} {{ $json.lastName }}`
– Email: `{{ $json.email }}`
– Company: `{{ $json.company }}`
– Source: `{{ $json.source }}`
– Status: `New`

[SCREENSHOT: Google Sheets node configuration with column mapping]

Now connect both the Slack node and the Google Sheets node to the HubSpot node output. These two can run in parallel since they do not depend on each other.

Testing and Debugging Tips

Before you activate this workflow for production traffic, run through these checks:

1. Use the built-in test runner: Click “Test Workflow” and send a test payload through your Webhook URL. Check the output of every node.
2. Check error handling: Send a request with a missing email field. The IF node should route it to the false branch.
3. Verify HubSpot: Open your HubSpot contacts and confirm the test contact was created with all fields populated correctly.
4. Check your inbox: Make sure the welcome email arrived and the personalization tokens rendered properly (no `{{ $json.firstName }}` showing as literal text).
5. Review Slack: Confirm the notification appeared in the correct channel with the right formatting.
6. Inspect Google Sheets: Verify the new row was appended with accurate data.

Common issues I ran into:

Webhook not receiving data: Make sure your form is sending a `POST` request with `Content-Type: application/json`. Some form builders send `application/x-www-form-urlencoded` by default.
HubSpot duplicate errors: Use the “Create or Update” operation instead of just “Create.” This upserts based on email address.
Slack rate limits: If you are processing a high volume of leads, consider batching Slack notifications or sending a summary every 15 minutes instead of one per lead.
Expression errors: If you see `[ERROR]` in a node, click into it and check the expressions panel. Usually it is a typo in the field name (e.g., `$json.firstname` vs `$json.firstName`).

Advanced Additions

Once your basic CRM automation with n8n is running smoothly, here are three upgrades I have added to my own workflow:

Lead Scoring with a Function Node

Add a Code node between the IF node and HubSpot to assign a lead score:

“`javascript
const score = 0;
let points = score;

// Company provided = higher intent
if ($json.company && $json.company.length > 0) {
points += 20;
}

// Source weighting
const highIntentSources = [‘pricing-page’, ‘demo-request’, ‘contact-sales’];
if (highIntentSources.includes($json.source)) {
points += 30;
}

// Business email domain (not gmail/yahoo)
const freeEmailDomains = [‘gmail.com’, ‘yahoo.com’, ‘hotmail.com’, ‘outlook.com’];
const emailDomain = $json.email.split(‘@’)[1];
if (!freeEmailDomains.includes(emailDomain)) {
points += 25;
}

return {
…$json,
leadScore: points,
leadTier: points >= 50 ? ‘hot’ : points >= 25 ? ‘warm’ : ‘cold’
};
“`

Then use the `leadScore` value in your HubSpot contact properties and in your Slack message. I color-code my Slack notifications based on tier — hot leads get a red circle emoji, warm gets yellow, cold gets blue.

Conditional Follow-Up Paths

Add a Switch node after the lead scoring to route leads differently:

Hot leads (score >= 50): Send to a dedicated `#hot-leads` Slack channel and trigger a follow-up task in your project management tool.
Warm leads (25-49): Standard flow with welcome email and Slack notification.
Cold leads (< 25): Welcome email only, no Slack notification (reduces noise).

Automated Follow-Up Sequence

Use a Wait node to send a follow-up email 48 hours after the initial welcome:

1. After the welcome email node, add a Wait node set to 48 hours.
2. Connect it to another Send Email node with a follow-up message.
3. Before sending, add an HTTP Request node to check the contact’s status in HubSpot — if they have already replied or booked a call, skip the follow-up.

Complete Workflow JSON Export

Here is a simplified version of the workflow you can import directly into n8n. Go to your n8n editor, click the three-dot menu, select Import from JSON, and paste this:

“`json
{
“name”: “CRM Lead Capture Workflow”,
“nodes”: [
{
“parameters”: {
“httpMethod”: “POST”,
“path”: “crm-lead-capture”,
“responseMode”: “lastNode”,
“options”: {}
},
“name”: “Webhook”,
“type”: “n8n-nodes-base.webhook”,
“typeVersion”: 1,
“position”: [240, 300]
},
{
“parameters”: {
“conditions”: {
“string”: [
{
“value1”: “={{ $json.email }}”,
“operation”: “isNotEmpty”
},
{
“value1”: “={{ $json.firstName }}”,
“operation”: “isNotEmpty”
}
]
},
“combineOperation”: “all”
},
“name”: “Validate Data”,
“type”: “n8n-nodes-base.if”,
“typeVersion”: 1,
“position”: [460, 300]
},
{
“parameters”: {
“resource”: “contact”,
“operation”: “upsert”,
“email”: “={{ $json.email }}”,
“additionalFields”: {
“firstName”: “={{ $json.firstName }}”,
“lastName”: “={{ $json.lastName }}”,
“company”: “={{ $json.company }}”
}
},
“name”: “HubSpot”,
“type”: “n8n-nodes-base.hubspot”,
“typeVersion”: 1,
“position”: [680, 300],
“credentials”: {
“hubspotApi”: {
“id”: “YOUR_CREDENTIAL_ID”,
“name”: “HubSpot API”
}
}
},
{
“parameters”: {
“fromEmail”: “[email protected]”,
“toEmail”: “={{ $json.email }}”,
“subject”: “=Welcome, {{ $json.firstName }}!”,
“text”: “Hey {{ $json.firstName }},\n\nThanks for reaching out! We will be in touch within 24 hours.\n\nBest,\nJavier”
},
“name”: “Welcome Email”,
“type”: “n8n-nodes-base.emailSend”,
“typeVersion”: 1,
“position”: [920, 180]
},
{
“parameters”: {
“channel”: “#new-leads”,
“text”: “=:rocket: *New Lead:* {{ $json.firstName }} {{ $json.lastName }} ({{ $json.email }}) from {{ $json.company }}”,
“otherOptions”: {}
},
“name”: “Slack Notification”,
“type”: “n8n-nodes-base.slack”,
“typeVersion”: 1,
“position”: [920, 300],
“credentials”: {
“slackApi”: {
“id”: “YOUR_CREDENTIAL_ID”,
“name”: “Slack API”
}
}
},
{
“parameters”: {
“operation”: “append”,
“documentId”: “YOUR_SPREADSHEET_ID”,
“sheetName”: “Sheet1”,
“columns”: {
“Date”: “={{ $now.format(‘yyyy-MM-dd HH:mm:ss’) }}”,
“Name”: “={{ $json.firstName }} {{ $json.lastName }}”,
“Email”: “={{ $json.email }}”,
“Company”: “={{ $json.company }}”,
“Source”: “={{ $json.source }}”,
“Status”: “New”
}
},
“name”: “Google Sheets Log”,
“type”: “n8n-nodes-base.googleSheets”,
“typeVersion”: 2,
“position”: [920, 420]
}
],
“connections”: {
“Webhook”: {
“main”: [
[{ “node”: “Validate Data”, “type”: “main”, “index”: 0 }]
]
},
“Validate Data”: {
“main”: [
[{ “node”: “HubSpot”, “type”: “main”, “index”: 0 }],
[]
]
},
“HubSpot”: {
“main”: [
[
{ “node”: “Welcome Email”, “type”: “main”, “index”: 0 },
{ “node”: “Slack Notification”, “type”: “main”, “index”: 0 },
{ “node”: “Google Sheets Log”, “type”: “main”, “index”: 0 }
]
]
}
}
}
“`

Note: You will need to replace `YOUR_CREDENTIAL_ID` and `YOUR_SPREADSHEET_ID` with your actual values after importing.

Frequently Asked Questions

Can I use this n8n CRM workflow with a CRM other than HubSpot?

Absolutely. n8n has native nodes for Salesforce, Pipedrive, Zoho CRM, Freshsales, and many others. The workflow structure stays exactly the same — you just swap out the HubSpot node for your CRM of choice and map the fields accordingly. You can also use the HTTP Request node to connect to any CRM that has a REST API.

Is n8n free for building CRM automations?

n8n is open-source and free to self-host. If you prefer a managed solution, n8n Cloud offers a free tier that is generous enough to handle this workflow for most small teams. The cloud version also removes the overhead of managing your own server, SSL certificates, and updates.

How do I handle errors in production?

I recommend adding an Error Trigger workflow that catches any failures across all your workflows and sends you a notification (email or Slack). In the workflow itself, you can also enable Retry on Fail on individual nodes — I set this to 3 retries with a 60-second wait on the HubSpot and email nodes, since transient API errors are common. For a deeper dive, see my n8n review where I cover reliability in production.

Wrapping Up

Building a CRM workflow in n8n took me about 45 minutes the first time. Now I can spin up a new variation in under 10 minutes. The time savings are real — I estimate this single workflow saves me 7-8 hours per week that I used to spend on manual data entry, copy-pasting, and context-switching between tabs.

If you have not tried n8n yet, the fastest way to get started is with the cloud version. You can have this entire workflow running today without touching a server.

The key takeaway: automate the boring stuff first. CRM data entry is the lowest-hanging fruit for any sales team, and once you see how easy it is to build an n8n CRM workflow, you will start automating everything else too.

Have questions about this workflow? Found a way to improve it? I would love to hear about it.

Deja un comentario