Why Local Business Data Goes Stale (The Lead Decay Problem)
B2B sales teams and lead generation agencies often treat prospect lists as static spreadsheets. However, local business databases decay at an astonishing rate. Every single month across North America and Europe:
New Entrants & Openings
Roughly 8–12% of local service companies (roofers, HVAC, solar, dental clinics) open their doors or launch second locations each quarter. Reaching them first yields the highest conversion rates.
Closures & Rebranding
Businesses relocate, rebrand, or permanently shutter. Outdated contact lists waste SDR calling hours and cause email domain reputation hits due to bounced cold emails.
Review Velocity & Reputation
Customer ratings shift weekly. A contractor rising from 3.8 to 4.7 stars with 80 new reviews indicates aggressive growth and budget for your software or marketing services.
Website & Domain Changes
Local companies upgrade from generic free websites to custom domains and professional email systems, unlocking new direct channels for outbound sales teams.
Instead of doing sporadic manual exports every few months, top-performing agencies set up scheduled recurring scrapes. By automating weekly searches across their core territories, they capture newly listed companies within days of appearing on Google Maps.
Manual Scraping vs. Scheduled Recurring Webhooks
How does a manual workflow compare to an automated webhook architecture? Let's break down the operational differences:
| Workflow Step | Manual Export Workflow | Scheduled Webhook Pipeline |
|---|---|---|
| Execution Trigger | Someone remembers to log in, type search queries, and click export. | Zero-touch cron timer (e.g., Monday 7:00 AM) triggers an automated API call. |
| Job Notification | Waiting for browser downloads or repeatedly checking status screens. | Instant HTTP POST webhook callback arrives the exact second extraction finishes. |
| Duplicate Handling | Manual Excel VLOOKUP formulas to remove businesses already in your CRM. | Bolt Scraper automatically deduplicates results using unique Google place_ids. |
| Lead Delivery | CSV files downloaded to local hard drives and manually imported. | Direct streaming into Postgres, Supabase, Google Sheets, or CRM via webhook payload. |
The 3-Component Recurring Pipeline Architecture
Building a resilient recurring scraping engine requires three simple components:
1. Scheduler (Cron / Cloud Function / GitHub Action)
Fires a POST request to https://boltscraper.com/api/v1/maps/jobs on your desired schedule (e.g., every Monday morning). Contains your target keywords, website scraping preferences, and your webhook callback URL.
2. Bolt Scraper Cloud Engine
Processes your search queries in parallel across distributed cloud workers. Crawls business websites for direct emails and social links, and automatically deduplicates listings via place_id.
3. Webhook Receiver Endpoint
A lightweight server endpoint (Node.js, Python, or no-code webhook) that receives the signed gmap_job.completed payload and automatically writes new records into your database or CRM.
Step 1: Setting Up the Scheduled Job Trigger
You can trigger your recurring scrape using a standard crontab, a serverless cloud function, or a lightweight scheduled script. Here is a production-ready Python script that launches a recurring multi-location scrape:
import os
import requests
# Bolt Scraper API Endpoint & Credentials
API_URL = "https://boltscraper.com/api/v1/maps/jobs"
API_KEY = os.getenv("BOLT_API_KEY", "bs_YOUR_API_KEY")
# Webhook receiver that will handle the completed job notification
WEBHOOK_CALLBACK = "https://api.yourdomain.com/webhooks/bolt-scraper"
def trigger_weekly_scrape():
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
payload = {
"keywords": [
"HVAC contractors in Charlotte, NC",
"HVAC contractors in Raleigh, NC",
"HVAC contractors in Greensboro, NC"
],
"scrape_websites": True, # Crawls websites for contact emails and social media
"ultra_mode": True, # Optional: up to 3x more leads (Standard mode already bypasses the 120-lead cap)
"webhook_url": WEBHOOK_CALLBACK
}
response = requests.post(API_URL, headers=headers, json=payload)
data = response.json()
if response.status_code == 200 and data.get("success"):
print(f"Scrape initiated! Job ID: {data['job_id']}")
print(f"Keywords queued: {data['keywords_count']}")
else:
print(f"Failed to start job: {data}")
if __name__ == "__main__":
trigger_weekly_scrape() To run this script automatically every Monday at 7:00 AM, add a single entry to your server's crontab:
# Run weekly Google Maps discovery every Monday at 07:00 UTC
0 7 * * 1 /usr/bin/python3 /opt/leads/schedule_gmap_scrape.py >> /var/log/lead_scrape.log 2>&1 Step 2: Building the Webhook Callback Receiver
Once Bolt Scraper finishes extracting businesses, scraping company websites, and deduplicating records in the cloud, it dispatches an HTTP POST event to your webhook_url.
Webhook Headers Sent by Bolt Scraper
X-Bolt-Event: gmap_job.completed: Event identifier.X-Bolt-Signature: sha256=...: HMAC SHA-256 signature calculated over the raw request body using your webhook secret.Content-Type: application/json: UTF-8 JSON payload.
Here is an Express (Node.js) server listening for webhook events and verifying the signature:
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');
const app = express();
const WEBHOOK_SECRET = process.env.BOLT_WEBHOOK_SECRET || 'your_whsec_secret';
// Preserve raw body buffer for HMAC signature verification
app.use(express.json({
verify: (req, res, buf) => { req.rawBody = buf; }
}));
app.post('/webhooks/bolt-scraper', async (req, res) => {
const signature = req.headers['x-bolt-signature'];
const eventType = req.headers['x-bolt-event'];
// 1. Verify HMAC SHA-256 Signature (Optional but recommended)
if (signature && WEBHOOK_SECRET) {
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
const digest = 'sha256=' + hmac.update(req.rawBody).digest('hex');
if (signature !== digest) {
return res.status(401).send('Invalid webhook signature');
}
}
// 2. Handle gmap_job.completed event
if (eventType === 'gmap_job.completed') {
const { job_id, total_leads, download_csv_url, leads } = req.body;
console.log(`Job ${job_id} finished with ${total_leads} unique business leads.`);
// Process inline leads or download the complete CSV export
if (download_csv_url) {
console.log(`Downloading full CSV from: ${download_csv_url}`);
// Ingest CSV directly into your master database or CRM
await syncLeadsToDatabase(download_csv_url);
}
}
// Acknowledge receipt promptly with HTTP 200
res.status(200).json({ received: true });
});
async function syncLeadsToDatabase(csvUrl) {
// Fetch CSV stream and upsert records into Postgres / Airtable
const response = await axios.get(csvUrl, { responseType: 'stream' });
// Records are already deduplicated in cloud by Bolt Scraper!
}
app.listen(3000, () => console.log('Webhook receiver listening on port 3000')); Zero-Effort Deduplication: How Place ID Protects Your Database
The biggest fear with scheduled recurring scraping is bloating your database with redundant records. If you search for contractors every week, won't you receive the same companies over and over?
Bolt Scraper Automatically Deduplicates in the Cloud
You don't have to write custom deduplication algorithms or clean overlapping CSV files. Every business listing returned includes Google's permanent, globally unique place_id. Bolt Scraper automatically deduplicates results across overlapping query boundaries in the cloud so you only receive clean, unique records.
When syncing leads into your own database (PostgreSQL, MySQL, Supabase, or MongoDB), use the place_id as your unique index for idempotent upserts:
-- Create leads table with unique place_id constraint
CREATE TABLE IF NOT EXISTS gmap_leads (
place_id VARCHAR(255) PRIMARY KEY,
business_name VARCHAR(255) NOT NULL,
phone VARCHAR(50),
website TEXT,
email VARCHAR(255),
address TEXT,
rating NUMERIC(2, 1),
reviews_count INTEGER,
last_scraped_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Safe upsert: Insert fresh leads or update review ratings on existing leads
INSERT INTO gmap_leads (place_id, business_name, phone, website, email, rating, reviews_count)
VALUES ('ChIJN1t_tDeuEmsRUsoyG83frY4', 'Alpha HVAC Services', '+1-704-555-0199', 'https://alphahvac.com', '[email protected]', 4.8, 142)
ON CONFLICT (place_id)
DO UPDATE SET
rating = EXCLUDED.rating,
reviews_count = EXCLUDED.reviews_count,
last_scraped_at = CURRENT_TIMESTAMP; Connecting Webhooks to Google Sheets & No-Code CRMs
If your team uses Google Sheets or Airtable instead of a raw SQL database, you can connect your webhook receiver directly via Make.com, n8n, or Zapier:
1. Webhook Trigger
Create a Custom Webhook module in Make/n8n and paste the URL into your Bolt Scraper webhook_url field.
2. Parse CSV / Leads
Use the CSV Parse module to unpack incoming lead records or iterate over the leads array payload.
3. Append & Notify
Add new rows to Google Sheets and send a Slack notification: "180 new HVAC leads added this morning!"
To dive into the full parameter list, JSON responses, and HTTP response codes, explore our flagship Google Maps Scraper API guide and the official Bolt Scraper API documentation. If you prefer configuring visual drag-and-drop webhooks without running cron servers, see our Google Maps scraper n8n / Make integration guide. For sweeping hundreds of regions simultaneously, review our blueprint for multi-city lead generation at scale.
Part of the Google Maps Scraping Playbook
Explore the other core guides in this topic cluster to build robust, automated prospecting pipelines:
Set Up Your Automated Lead Scrapes Today
Never run manual searches again. Connect the Bolt Scraper API to your scheduling workflows, bypass the 120-lead limit natively, and receive fresh leads on autopilot.
Instant cloud setup • Webhooks & REST API • Test live search API for free & view docs
Frequently Asked Questions
GET /api/v1/maps/jobs/{job_id} or download the CSV at any time. place_id. When storing records in your own CRM, indexing by place_id allows seamless, zero-duplicate upserts.