Google Sheets + HubSpot: enriched leads, no cleanup
Your lead lists look fine until you try to use them. Missing emails, half-filled names, random company fields, and duplicates that somehow multiply after every import.
Demand gen managers feel it when “launch week” turns into spreadsheet triage. Sales ops gets stuck cleaning the CRM instead of improving it. And if you run a small agency, you’re the one doing both. This Sheets HubSpot enrichment automation turns raw rows into validated HubSpot contacts you can actually follow up with.
You’ll see how the workflow reads a Google Sheet, enriches leads in Surfe (in safe batches), filters out bad data, and only then updates HubSpot. Clean in. Clean out.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: Google Sheets + HubSpot: enriched leads, no cleanup
flowchart LR
subgraph sg0["Google Drive Flow"]
direction LR
n0@{ icon: "mdi:database", form: "rounded", label: "Google Sheets", pos: "b", h: 48 }
n1@{ icon: "mdi:play-circle", form: "rounded", label: "Google Drive Trigger", pos: "b", h: 48 }
n2@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Filter: phone AND email", pos: "b", h: 48 }
n3["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Extract list of peoples from.."]
n4@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Is enrichment complete ?", pos: "b", h: 48 }
n5["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Surfe Bulk Enrichments API"]
n6["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Surfe check enrichement status"]
n7@{ icon: "mdi:message-outline", form: "rounded", label: "Gmail", pos: "b", h: 48 }
n8@{ icon: "mdi:swap-vertical", form: "rounded", label: "Split Batch - 500", pos: "b", h: 48 }
n9["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/hubspot.svg' width='40' height='40' /></div><br/>HubSpot: Create or Update"]
n10@{ icon: "mdi:cog", form: "rounded", label: "Wait 30 secondes", pos: "b", h: 48 }
n11["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Prepare JSON Payload Enrichm.."]
n0 --> n8
n10 --> n6
n8 --> n7
n8 --> n11
n1 --> n0
n2 --> n9
n4 --> n3
n4 --> n10
n9 --> n8
n5 --> n6
n6 --> n4
n11 --> n5
n3 --> n2
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n1 trigger
class n2,n4 decision
class n0 database
class n5,n6 api
class n3,n11 code
classDef customIcon fill:none,stroke:none
class n3,n5,n6,n9,n11 customIcon
The Problem: Lead enrichment turns into CRM cleanup
Manually enriching leads is one of those tasks that looks small, then eats your afternoon. You export a list from an event tool or partner spreadsheet, paste it into a new sheet, hunt for missing company domains, and still end up with contacts that bounce or can’t be called. Worse, you import anyway because the team needs “something,” and now HubSpot has contacts with no email, no phone, and just enough info to create duplicates later. It’s draining. It also makes follow-ups slower because reps don’t trust the data.
The friction compounds. Here’s where it breaks down.
- Someone has to enrich records one-by-one (or juggle multiple tools) before anything is ready for outreach.
- You can’t easily tell which rows are “good enough,” so questionable leads slip into HubSpot and clog lists.
- Bulk enrichment often hits API limits, which means retries, splitting files, and a lot of babysitting.
- Every manual import increases the chance of duplicates and inconsistent property mapping.
The Solution: Upload a sheet, enrich in Surfe, sync only validated contacts
This workflow flips the process around. Instead of pushing raw leads into HubSpot and fixing them later, you drop a Google Spreadsheet into a specific Google Drive folder and let n8n do the heavy lifting. n8n reads the rows, groups them into batches of 500 (so Surfe’s bulk API stays happy), and sends the batch for enrichment. While Surfe works, the workflow checks job status on a short delay loop until results are ready. Then it parses the enriched data, keeps only contacts that have both an email and a phone number, and upserts those into HubSpot. Finally, it sends you a Gmail confirmation so you know the run completed.
The workflow starts with a Google Drive file watcher, so you don’t have to click anything in n8n. It enriches leads through Surfe’s Bulk Enrichment API, then filters and upserts into HubSpot. Your CRM gets the “ready to use” subset only.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say you upload a sheet of 1,000 leads from an event. Manually, enrichment is easily 2 minutes per lead once you factor in lookups, copy-paste, and formatting, which is about 30 hours of work before you even import. With this workflow, you drop the spreadsheet into the Drive folder (2 minutes), n8n processes two batches of 500, and Surfe runs in the background while the workflow polls for completion. You typically come back to a HubSpot list that’s already populated with validated contacts, plus an email telling you it’s done.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Google Drive to trigger on new spreadsheet uploads
- Google Sheets to read rows from the uploaded sheet
- Surfe API key (get it from your Surfe dashboard)
- HubSpot to create or update contacts in your CRM
- Gmail to send a completion notification email
Skill level: Intermediate. You’ll connect a few credentials and confirm your sheet columns match the expected format.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
A new spreadsheet lands in the right Drive folder. The Google Drive Trigger (“Drive File Watcher”) detects the file automatically, so the workflow can run without anyone opening n8n.
Rows are pulled and prepared for bulk enrichment. n8n reads the sheet, then splits the leads into batches of 500 and builds the JSON payload Surfe expects (including name plus company name or company domain, and LinkedIn URL if you have it).
Surfe enrichment runs, then n8n waits and checks status. A bulk request kicks off the Surfe job. The workflow pauses for about 30 seconds, checks the job status, and repeats until enrichment is done.
Validated contacts get synced to HubSpot. n8n parses the returned people results, filters out entries missing either email or phone, and upserts the rest into HubSpot. A Gmail message confirms completion.
You can easily modify the validation rule (email only vs. email + phone) to match how your team qualifies leads. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Google Drive Trigger
This workflow starts when a new Google Sheet is created in a specific Drive folder.
- Add and open Drive File Watcher.
- Set Event to
fileCreated. - Set Trigger On to
specificFolder. - Set Folder To Watch to the folder ID used in the node’s
folderToWatchfield (replace[YOUR_ID]). - Credential Required: Connect your Google Drive credentials.
Step 2: Connect Google Sheets
The newly created Sheet ID is used to read rows for enrichment.
- Open Retrieve Sheet Rows.
- Set Document ID to
{{ $json.id }}. - Set Sheet Name to
Sheet1(valuegid=0). - Credential Required: Connect your Google Sheets credentials.
Step 3: Set Up Batch Processing and Enrichment Requests
Rows are processed in batches, then sent to Surfe for bulk enrichment with a retry loop.
- Open Batch Records 500 and set Batch Size to
500. - Connect Batch Records 500 to Build Enrichment Payload and Send Completion Email — these run in parallel.
- In Build Enrichment Payload, review the JavaScript that maps
item.json.linkedin_urlinto the request payload. - In Bulk Enrich Request, set URL to
https://api.surfe.com/v2/people/enrich, Method toPOST, and JSON Body to{{ $json }}. - In Bulk Enrich Request, set the Authorization header to
Bearer [CONFIGURE_YOUR_TOKEN]with your Surfe token. - In Enrichment Status Check, set URL to
https://api.surfe.com/v2/people/enrich/{{ $json.enrichmentID }}. - In Check Enrichment Done, ensure the condition checks
{{ $json.status }}equalsCOMPLETED. - Connect the “false” path of Check Enrichment Done to Delay 30 Seconds, and keep Amount at
30seconds to poll again.
Authorization header in both nodes.Step 4: Configure Parsing, Filtering, and HubSpot Updates
Once enrichment is completed, results are parsed, filtered for usable contacts, and upserted into HubSpot.
- In Parse People Results, verify the JavaScript maps the first email and mobile phone into
emailandphone. - Open Filter Contact Details and keep the two conditions that ensure
{{ $json.phone }}and{{ $json.email }}are not empty. - Open Upsert HubSpot Contact and set Authentication to
appToken. - Set Email to
{{ $json.email }}and map additional fields like First Name to{{ $json.firstName }}and Company Name to{{ $json.companyName }}. - Credential Required: Connect your HubSpot credentials.
Step 5: Configure the Completion Email
When the batch flow completes, a notification email is sent.
- Open Send Completion Email.
- Set Send To to your email address (replace
[YOUR_EMAIL]). - Set Subject to
Your batch csv enrichment is done.. - Set Message to
Your batch csv enrichment is done.. - Credential Required: Connect your Gmail credentials.
Step 6: Test and Activate Your Workflow
Verify that the trigger, enrichment loop, and HubSpot updates work end-to-end before enabling production mode.
- Click Execute Workflow and upload a new Google Sheet into the watched folder to trigger Drive File Watcher.
- Confirm Retrieve Sheet Rows reads the data and Batch Records 500 starts processing items.
- Verify Bulk Enrich Request returns an
enrichmentIDand Enrichment Status Check eventually returnsCOMPLETED. - Check that Upsert HubSpot Contact creates or updates contacts with enriched data.
- Confirm the email from Send Completion Email is delivered after processing finishes.
- When successful, toggle the workflow to Active to run in production.
Common Gotchas
- Google Drive/Sheets OAuth permissions are picky. If the trigger fires but rows don’t load, check the credential in n8n and confirm the Google account has access to the target folder and file.
- If you’re using Wait nodes or external enrichment, processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- HubSpot upserts can fail quietly when your private app token lacks scopes. Double-check the Private App permissions for contacts read/write, then re-test the “Upsert HubSpot Contact” node.
Frequently Asked Questions
About an hour if your Google, Surfe, and HubSpot accounts are ready.
No. You’ll connect credentials and paste in your Surfe API key. The workflow’s code nodes are already set up for payload building and parsing.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in Surfe API costs based on your enrichment volume.
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes, but be intentional. You can change the “Filter Contact Details” logic to keep leads that only have email, then pass them to “Upsert HubSpot Contact.” Common customizations include requiring a company domain, mapping extra Surfe fields into HubSpot properties, and writing enriched results back to a “processed” Google Sheet for auditing.
Usually it’s the private app token scopes. Regenerate the token (or update scopes) so it includes contacts read/write, then update the credential used by the “Upsert HubSpot Contact” node. Also check that your HubSpot account hasn’t restricted the app, and watch for rate limiting if you’re pushing very large lists.
A lot. The workflow batches 500 records at a time, so lists of several thousand are normal as long as your Surfe plan and HubSpot API limits support it. On n8n Cloud Starter you’re capped by monthly executions, while self-hosting has no execution cap (it mostly depends on your server). Practically, the waiting/polling step is what sets the pace, not the parsing.
Often, yes. This workflow needs batching, looping, and polling a long-running job, and frankly that’s where no-code “quick zaps” start to get annoying or expensive. n8n handles branching logic cleanly, and you can self-host for unlimited runs if you process big lists. Zapier or Make can still be fine for tiny imports, like a 20-row sheet once a month. Talk to an automation expert if you want a quick recommendation based on your volume and tooling.
Once this is running, your team stops treating HubSpot like a junk drawer. Upload the file, get enriched contacts, move on.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.