# How to Schedule Recurring Google Maps Scrapes with Webhooks

> A complete technical guide to keeping local business lead pipelines fresh automatically. Set up weekly or monthly cron triggers, listen for instant webhook callbacks, and append new prospects directly into your database.

**URL:** https://boltscraper.com/blogs/guides/schedule-google-maps-scraping-webhooks/  
**Date Published:** 2026-09-19  
**Date Modified:** 2026-09-19  

---

## 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 quarter:

- **New Entrants & Openings**: 8–12% of local service companies (roofers, HVAC, dental clinics) open doors or expand to new locations every quarter. Reaching them first yields the highest conversion rates.
- **Closures & Rebranding**: Businesses relocate, rebrand, or permanently shutter. Outdated lists waste SDR calling hours and cause email domain reputation hits due to bounced emails.
- **Review Velocity & Reputation**: 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 agency services.
- **Website & Domain Changes**: Companies upgrade from generic social pages to custom domains and professional email systems, unlocking direct channels for outbound sales.

Instead of sporadic manual exports, top-performing teams schedule automated recurring scrapes to capture newly listed companies within days of appearing on Google Maps.

---

## Manual Scraping vs. Scheduled Recurring Webhooks

| Workflow Step | Manual Export Workflow | Scheduled Webhook Pipeline |
| :--- | :--- | :--- |
| **Execution Trigger** | Someone remembers to log in, type 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_id`s. |
| **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

1. **Scheduler (Cron / Cloud Function / GitHub Action)**: Fires an HTTP POST request to `https://boltscraper.com/api/v1/maps/jobs` on your schedule with target keywords and your webhook callback URL.
2. **Bolt Scraper Cloud Engine**: Processes 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 writes records into your database.

---

## Step 1: Setting Up the Scheduled Job Trigger

Here is a production-ready Python script that launches a recurring multi-location scrape:

```python
import os
import requests

API_URL = "https://boltscraper.com/api/v1/maps/jobs"
API_KEY = os.getenv("BOLT_API_KEY", "bs_YOUR_API_KEY")
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`:

```bash
# 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.

### Node.js / Express Webhook Receiver:

```javascript
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 stream full dataset from signed download_csv_url
        if (download_csv_url) {
            console.log(`Downloading full CSV from: ${download_csv_url}`);
            await syncLeadsToDatabase(download_csv_url);
        }
    }

    // Acknowledge receipt promptly with HTTP 200
    res.status(200).json({ received: true });
});

async function syncLeadsToDatabase(csvUrl) {
    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

Every listing returned by Bolt Scraper 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 database (PostgreSQL, MySQL, Supabase), use `place_id` as your unique primary key for idempotent upserts:

```sql
-- 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', 'contact@alphahvac.com', 4.8, 142)
ON CONFLICT (place_id) 
DO UPDATE SET 
    rating = EXCLUDED.rating,
    reviews_count = EXCLUDED.reviews_count,
    last_scraped_at = CURRENT_TIMESTAMP;
```

---

## Bolt Scraper Cloud Plans

- **Free**: $0/mo — 1,000 leads/mo, 5 keywords/scrape, 1 active job, CSV/Drive export.
- **Basic**: $19/mo — 50,000 leads/mo, 50 keywords/scrape, 1 active job, email & social extraction.
- **Professional**: $49/mo — 200,000 leads/mo, 500 keywords/scrape, 3 parallel jobs, Ultra Mode (up to 3x leads), API Access.
- **Business**: $99/mo — 600,000 leads/mo, 1,000 keywords/scrape, 3 parallel jobs, Ultra Mode, API Access, priority support.
- **Advanced**: $199/mo — 1,500,000 leads/mo, 2,000 keywords/scrape, 5 parallel jobs, Ultra Mode, API Access.

---

## Frequently Asked Questions

### What happens if my webhook endpoint is temporarily down?
Bolt Scraper retries failed webhook deliveries with exponential backoff. Additionally, scrape results are persisted safely in the cloud, so you can always poll `GET /api/v1/maps/jobs/{job_id}` or download the CSV at any time.

### How long are the signed CSV download URLs valid?
Signed CSV download URLs delivered in the webhook payload remain active for 24 hours. You can download the complete dataset immediately upon receiving the event or request a refreshed download link via the API.

### Do I have to deduplicate leads across repeated runs myself?
No. Bolt Scraper automatically deduplicates business listings across multiple target keywords and locations in the cloud using Google's unique `place_id`. When storing records in your own CRM, indexing by `place_id` allows seamless, zero-duplicate upserts.

### How many keywords can I schedule in a single job?
You can pass arrays containing dozens or hundreds of search queries in a single POST request. Bolt Scraper distributes them across parallel cloud execution threads and sends a single consolidated webhook callback when all queries complete.

---

## Related Topic Cluster Guides

- [Start For Free — 1,000 Leads/Mo](https://boltscraper.com/google-maps-scraper/)
- [Google Maps Scraper API Guide](https://boltscraper.com/blogs/guides/google-maps-scraper-api-automation/)
- [Multi-City Lead Generation at Scale](https://boltscraper.com/blogs/guides/build-local-lead-pipeline-maps-api/)
- [Google Places API Alternative / Cost Comparison](https://boltscraper.com/blogs/guides/google-places-api-vs-maps-scraper-api/)
- [Google Maps Scraper n8n / Make Integration](https://boltscraper.com/blogs/guides/connect-google-maps-api-make-n8n/)
- [Bolt Scraper API Documentation](https://boltscraper.com/google-maps-scraper/)
