← All posts
October 18, 2026 · 10 min read · The LeadMerlin Team

n8n Workflow: Google Maps Leads → Verified Emails → Google Sheets

Build an n8n workflow that searches local businesses with the LeadMerlin API, waits for enrichment, keeps only verified emails, and appends clean rows to Google Sheets.

This guide builds an n8n workflow that turns “dentists in Irvine” into a Google Sheet of leads with verified emails. It uses only standard n8n nodes: HTTP Request, Wait, If, Split Out, Filter and Google Sheets. You can run it on n8n Cloud or self-hosted.

The flow:

  1. Start a LeadMerlin search job
  2. Wait, then poll the job until it’s completed
  3. Fetch all results, following pagination
  4. Split the businesses into items and keep the ones with a verified, non-role email
  5. Append or update rows in Google Sheets

Before you start

  • A LeadMerlin API key (starts with lm_; the free plan includes 100 lead credits)
  • An n8n instance
  • A Google Sheet with a header row. We’ll use: name, owner, email, email_status, phone, phone_type, website, city, rating, reviews, cms, gbp_url

Create the credential once

In n8n, create a Header Auth credential:

  • Name: x-api-key
  • Value: your lm_… key

Every HTTP Request node below uses this credential. The key is never pasted into a node body.

Node 1: Trigger

Use a Manual Trigger while building, then switch to a Schedule Trigger if you want a weekly refresh. Add a Set node after it holding your inputs:

{ "keyword": "dentist", "location_codes": [1013883], "strictness": "moderate" }

1013883 is Irvine, CA. To find codes for other cities, call GET https://api.leadmerlin.com/api/v2/locations?q=your city once (it’s free) and copy the location_code. One job accepts up to five codes.

HTTP Request node:

  • Method: POST
  • URL: https://api.leadmerlin.com/api/v2/searches
  • Authentication: Generic → Header Auth → your credential
  • Send Body: JSON
{
  "keyword": "{{ $json.keyword }}",
  "location_codes": {{ JSON.stringify($json.location_codes) }},
  "strictness": "{{ $json.strictness }}"
}

The API answers 202 with a job_id. From this point you are charged one credit for each business that has a website. Listings without a website and off-niche listings are free.

Nodes 3–5: The polling loop

Enrichment crawls each business’s website, verifies emails and looks up phone line types, so a job takes a while. Typically that’s a minute or two, longer for big cities. Build a simple loop:

  1. Wait node: 20 seconds.
  2. HTTP Request node: GET https://api.leadmerlin.com/api/v2/jobs/{{ $('Start search').item.json.job_id }} with the same credential.
  3. If node: {{ $json.status }} is equal to completed.
    • true → continue to results
    • false → connect back to the Wait node

Add a second condition so a failed job doesn’t loop forever: route status = failed to a Stop and Error node.

The status response is worth logging, because it explains the result count:

{
  "status": "completed",
  "total_unique": 2,
  "enriched_count": 2,
  "skipped_count": 0,
  "off_niche_count": 0,
  "category_facets": { "Plumber": 2 },
  "progress": 100
}

(That sample comes from the free demo endpoint at /api/v2/demo/jobs/x. Point the nodes at /api/v2/demo/... while building and nothing is charged.)

Node 6: Fetch all results with pagination

HTTP Request node:

  • Method: GET
  • URL: https://api.leadmerlin.com/api/v2/jobs/{{ $('Start search').item.json.job_id }}/results
  • Query parameter: limit = 200

Open Options → Pagination:

  • Pagination mode: Update a Parameter in Each Request
  • Parameter: Query, name cursor, value {{ $response.body.next_cursor }}
  • Pagination complete when: Other → {{ $response.body.has_more === false }}

Each page has the shape { businesses: [...], next_cursor, has_more }.

Node 7: Split Out

Split Out node on the field businesses. Each business becomes its own item.

Node 8: Keep only the leads you can email

Filter node with two conditions (AND):

  • {{ $json.contact.all_emails.find(e => e.email === $json.email)?.status }} is equal to verified
  • {{ $json.contact.all_emails.find(e => e.email === $json.email)?.is_role_based }} is false

email is the best address LeadMerlin picked for the business. contact.all_emails[] lists every address found, each with:

  • status: verified, catch-all, invalid or unverified
  • is_role_based: info@, office@ and similar
  • is_free_provider: Gmail, Yahoo and so on

Loosen the filter if you also want catch-all addresses, which accept everything and can’t be confirmed mailbox by mailbox.

Node 9: Shape the row

A Set node maps the nested JSON to flat columns:

ColumnExpression
name{{ $json.name }}
owner{{ $json.owner }}
email{{ $json.email }}
email_statusverified
phone{{ $json.phone }}
phone_type{{ $json.phone_type }}
website{{ $json.website }}
city{{ $json.contact.city }}
rating{{ $json.reputation?.rating }}
reviews{{ $json.reputation?.reviews }}
cms{{ $json.web.tech?.cms }}
gbp_url{{ $json.gbp_url }}

Node 10: Google Sheets

Google Sheets node → Append or Update Row:

  • Column to match on: website (or gbp_url for businesses without one)
  • Mapping: Map Automatically, since the Set node already uses your header names

Matching on website means re-running the workflow next month updates existing rows instead of duplicating them.

Variations worth building

  • Qualify by tech stack. Add a Filter on web.tech.chat being empty or web.tech.cms being wix, which is useful if you sell chat widgets or websites. See how to qualify leads with tech stack signals.
  • Enrich a list you already have. Swap Node 2 for POST /v2/enrichments with { "leads": [{ "domain": "example.com" }] } (up to 100 per request). It uses the same job polling and results flow.
  • Send to a CRM. Replace the Sheets node with HubSpot, Pipedrive or an HTTP call to your outreach tool.
  • Multi-city runs. Pass up to five location codes in one job instead of looping cities.

Costs and limits to keep in mind

  • One lead credit per business with a website. Website-less and off-niche listings are free, and failed enrichments are refunded.
  • If a search matches more businesses than your balance covers, it enriches the top-ranked ones your balance pays for and reports the rest as over_budget_count. Pass max_leads to cap a search yourself.
  • Polling, locations and suggest calls don’t use credits, but a 20-second wait between polls is plenty.

Before you email anyone on the sheet, read our CAN-SPAM checklist for cold email to local businesses.

Try LeadMerlin free.

Search any niche across one city or many. First 100 leads free — no credit card.

Start free