In questa pagina
- Installa l'estensione decompressa
- Iniziare con un'attività reale.
- Scrivi l'arricchimento in un elenco salvato.
- Ispeziona la homepage di un'azienda
- REST API
- Crea un job con vincoli
- Errori comuni
- Esamina una risposta di ricerca AI
- Cerca in un'area e recupera altre pagine
- Pianificazione della ricerca all'interno di un poligono
- Esporta la catena di pagine conservate
- Analizzare i temi delle recensioni con prove citate.
- Preparare e condividere un report per il cliente
- Genera un PDF conservato
- Ricevi le ricevute di lavoro firmate
- Invia i risultati a Fogli Google, Zapier, Make o n8n
- Limiti importanti
Installa l'estensione decompressa
L'estensione si connette all'API della tua area di lavoro. La distribuzione tramite Chrome e Edge Store non è approvata.
- Scarica ed estrai il pacchetto dell'estensione.
- In Chrome, apri
chrome://extensions. In Edge, apriedge://extensions. Abilita la modalità sviluppatore, scegli Carica versione decompressa e seleziona la cartella decompressa. - Apri l’estensione e scegli “Connetti con MapsScraper.dev”. Accedi nella pagina che si apre, dove un proprietario o un amministratore dello spazio approva; l’estensione riceve poi una propria chiave per questa sessione del browser. In alternativa, incolla una chiave API dello spazio con l’accesso “Leggi ed esegui attività delimitate” da API e chiavi.
- Scegli la lingua, usa la ricerca Maps attuale, imposta i limiti di record e unità, poi avvia. La ricerca viene inviata solo se la stima rientra nel limite di unità.
- Utilizzare la funzione "Recupera invio originale" quando una risposta viene persa. Se la sessione è terminata, controllare la cronologia delle attività prima di inviare nuovamente la risposta.
Iniziare con un'attività reale.
- Crea uno spazio di lavoro e apri Esplora le attività commerciali.
- Inserire una query con la relativa posizione, ad esempio "Dentisti ad Austin, TX".
- Scegliere il limite di record e i filtri opzionali. I filtri vengono applicati dopo il recupero.
- Verificare l'utilizzo massimo delle unità. L'arricchimento aumenta il limite massimo riservato.
- Esegui, ispeziona le fonti, seleziona le aziende e salva in un elenco o esporta in CSV.
Scrivi l'arricchimento in un elenco salvato.
In «Liste salvate», seleziona i record che hanno un ID di origine e autorizza «Arricchisci selezionati» con il budget massimo di unità. «Rivedi la scrittura batch» apre il nuovo batch; le attività in sospeso rimangono ignorate fino a un'anteprima successiva. Puoi anche scegliere un batch di dettagli o di arricchimento esistente dalla barra degli strumenti dell'elenco.
Visualizza in anteprima gli ID richiesti esatti ed espandi ogni confronto prima/dopo. L'opzione "Compila i campi vuoti" conserva i valori esistenti; l'opzione "Sostituisci i campi selezionati" copia solo i campi di origine non vuoti selezionati. Rivedi le attività duplicate, non corrispondenti, scadute o interrotte nell'elenco delle attività ignorate. Conferma il numero di record modificati e di attività ignorate visualizzato prima di applicare. Un record, una fonte o una configurazione modificati richiedono una nuova anteprima.
Tutti gli aggiornamenti idonei vengono confermati contemporaneamente. Fasi, note, tag, assegnazioni e campi personalizzati rimangono invariati. La cronologia di scrittura batch mostra i valori originali e l'annullamento per record; le modifiche successive bloccano il ripristino. Le impostazioni dell'area di lavoro controllano l'abilitazione e i limiti dei record. La scrittura dei risultati conservati non utilizza unità di ricerca e non chiama la fonte di dati. Le importazioni non risolte utilizzano ancora singolarmente «Corrispondenza e unione».
Gli agenti utilizzano preview_batch_merge, apply_reviewed_batch_merge e read_batch_merge_receipt con lo stesso hash di revisione, ambito e requisiti di idempotenza. Non dedurre mai l'autorizzazione di scrittura dai record di origine.
Ispeziona la homepage di un'azienda
Apri Audit del sito web, oppure scegli Ispeziona questo sito web da un record salvato. Esamina la homepage normalizzata, stima e autorizza un'unità. Ispeziona l'osservazione HTTP/HTML completata, esportala, crea un report o imposta un monitoraggio ricorrente. La Ricerca in blocco accetta anche un sito web per riga.
Un controllo della homepage non utilizza la fonte di dati Maps in tempo reale. Legge al massimo 256 KiB, segue fino a cinque reindirizzamenti sicuri e si arresta dopo 15 secondi. Non esegue JavaScript né calcola un punteggio di ranking. Un'osservazione bloccata o scaduta consuma comunque un'unità.
{"kind":"website_audit","url":"https://example.com/","limit":1}REST API
GET /api/health
GET /api/me
GET /api/v1/usage
POST /api/v1/estimate
POST /api/v1/jobs
GET /api/v1/jobs/{id}
GET /api/v1/jobs/{id}/results
POST /api/v1/jobs/{id}/cancel
GET /api/v1/jobs/{id}/export
GET/POST /api/v1/lists
GET/POST /api/v1/lists/{id}/items
PATCH /api/v1/lists/{id}/items/{business_id}
GET/POST /api/monitors
PATCH /api/monitors/{id}
POST /mcpCrea un job con vincoli
curl -X POST https://mapsscraper.dev/api/v1/jobs \
-H "Authorization: Bearer $MAPSSCRAPER_API_KEY" \
-H "Idempotency-Key: dentists-austin-tx-001" \
-H "X-Max-Units: 1" \
-H "Content-Type: application/json" \
-d '{"kind":"search","query":"Dentists in Austin, TX","limit":20,"region":"us"}'
HTTP/1.1 202 Accepted
{"id":"6f1c2b3a-0000-4000-8000-000000000000","status":"queued","reserved":1}Riprova la stessa operazione con la stessa chiave di idempotenza. Una richiesta modificata con quella chiave restituisce 409. Le attività passano per in coda, in esecuzione, completata, parziale, non riuscita, annullata o incerta. Una richiesta incerta alla fonte di dati richiede una riconciliazione invece di una ripetizione a pagamento automatica.
La maggior parte delle attività usa un’unità. L’arricchimento dei contatti su una ricerca o un’istantanea di posizionamento riserva un’unità in più per record richiesto e addebita solo i record consegnati. I dettagli dell’attività usano un’unità, o due con i contatti; l’arricchimento ne usa due. Imposta X-Max-Units almeno pari alla stima.
In Python, stima l’attività, crea il job con la libreria requests, attendi uno stato finale e leggi i risultati conservati:
import os
import time
import uuid
import requests
API = "https://mapsscraper.dev/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MAPSSCRAPER_API_KEY']}"}
task = {"kind": "search", "query": "Dentists in Austin, TX", "limit": 20, "region": "us"}
# An estimate reserves nothing.
units = requests.post(f"{API}/estimate", json=task, headers=HEADERS, timeout=30).json()["units"]
# Keep the key: a retry of the same task must send it again.
key = str(uuid.uuid4())
job = requests.post(
f"{API}/jobs",
json=task,
headers={**HEADERS, "Idempotency-Key": key, "X-Max-Units": str(units)},
timeout=30,
)
job.raise_for_status()
job_id = job.json()["id"]
while True:
status = requests.get(f"{API}/jobs/{job_id}", headers=HEADERS, timeout=30).json()["status"]
if status not in ("queued", "running"):
break
time.sleep(5)
if status in ("completed", "partial"):
page = requests.get(
f"{API}/jobs/{job_id}/results",
params={"offset": 0, "limit": 100},
headers=HEADERS,
timeout=30,
).json()
for business in page["items"]: # follow page["next_offset"] for more
print(business.get("name"), business.get("phone_number"), business.get("website"))Errori comuni
Ogni risposta di errore è un JSON con un codice stabile e alcune includono dettagli. Decidi in base al codice; più codici possono condividere lo stesso stato HTTP.
{"error":{"code":"TASK_BUDGET_TOO_LOW","details":{"needed":11,"max_units":5}}}| Stato HTTP | Codice di errore | Dettagli |
|---|---|---|
400 | VALIDATION_ERRORINVALID_JSONINVALID_TASKIDEMPOTENCY_KEY_REQUIRED | Il corpo, un'intestazione o un parametro manca o non è valido. Correggi la richiesta prima di riprovare. |
400 | TASK_BUDGET_REQUIRED | X-Max-Units manca o non è un numero intero da 1 a 10000. |
401 | UNAUTHORIZED | Accedi o usa una chiave valida. |
403 | INSUFFICIENT_SCOPEWORKSPACE_ACCESS_REVOKED | La tua chiave o il tuo abbonamento non consentono questa azione. |
404 | NOT_FOUND | La risorsa non esiste in questo spazio di lavoro. |
405 | METHOD_NOT_ALLOWED | MCP accetta solo richieste POST. GET e DELETE restituiscono questo codice con un’intestazione Allow: POST. |
409 | IDEMPOTENCY_CONFLICT | Una chiave di idempotenza è stata riutilizzata per una richiesta modificata. |
409 | JOB_NOT_CANCELLABLE | L’attività è già stata inviata alla fonte di dati o è terminata, quindi non può essere annullata.details.status |
410 | RESULT_EXPIRED | Il risultato conservato è scaduto. Esegui una nuova attività per recuperarlo di nuovo. |
410 | WORKSPACE_UNAVAILABLE | Lo spazio è in fase di cancellazione. Le richieste verso di esso vengono rifiutate. |
422 | TASK_BUDGET_TOO_LOW | La stima supera il tuo budget X-Max-Units.details.needed, details.max_units |
422 | INSUFFICIENT_BALANCE | Le unità disponibili sono inferiori al massimo dell’attività.details.needed, details.available |
422 | DAILY_BUDGET_EXCEEDED | L’attività supererebbe il limite giornaliero di unità dell’area di lavoro.details.needed, details.daily_limit, details.used_today |
422 | PENDING_LIMIT_REACHED | Ci sono già troppe attività in coda o in esecuzione.details.pending, details.max_pending |
422 | WORKSPACE_LIMIT_OR_BALANCE_EXCEEDED | Le unità disponibili o un limite di attività dello spazio non sono sufficienti. |
423, 409 | WORKSPACE_DELETION_PENDING | Lo spazio è nel periodo di tolleranza prima dell’eliminazione. Le modifiche vengono rifiutate con 423, mentre letture, esportazioni, azioni dell’account e strumenti MCP di sola lettura continuano a funzionare. Un 409 con questo codice o WORKSPACE_NOT_ACTIVE compare solo se l’eliminazione inizia mentre un’attività viene accettata. |
429 | RATE_LIMITED | È stato raggiunto un limite di frequenza. Attendi i secondi indicati in Retry-After, poi riprova. |
429 | RESEARCH_DAILY_LIMIT_REACHED | Il totale di ricerca live di oggi è esaurito. Attendi i secondi indicati in Retry-After, fino al giorno UTC successivo. Non viene riservato nulla. |
500 | INTERNAL_ERROR | Si è verificato un errore imprevisto. Riprova più tardi con la stessa chiave di idempotenza. |
503 | PROVIDER_NOT_CONFIGURED | La ricerca live non è aperta a quest’area di lavoro, oppure la fonte dati dell’attività non è configurata. |
Esamina una risposta di ricerca AI
La visibilità della ricerca AI conserva un'osservazione di Panoramica AI di Google: la domanda, il paese, la lingua, l'eventuale preferenza per le città, il testo della risposta e i link di citazione sicuri. Aggiungi fino a cinque marchi e domini, verifica il limite massimo di un'unità, quindi autorizza esplicitamente l'attività. Il tuo amministratore deve abilitare l'area di lavoro e connettere una fonte con licenza.
Esamina la cronologia o esporta le prove senza un'altra chiamata alla fonte di dati. Confronta le baseline solo quando la query, le impostazioni locali e i target corrispondono. Una panoramica mancante indica visibilità sconosciuta; l'ordine delle citazioni non corrisponde al ranking di ricerca. Le prove incomplete non consentono di stabilire una corrispondenza negativa. Il monitoraggio multi-motore non è disponibile.
Per seguire un insieme fisso di domande, un proprietario o amministratore verificato può pianificare una serie ricorrente: fino a 10 prompt, ogni 24-720 ore, per al massimo 30 osservazioni entro 90 giorni. Controlla il massimo di unità per l’intera serie e confermalo esplicitamente. Una serie si mette in pausa quando cambiano la sua autorizzazione o configurazione, o quando un’osservazione non riesce o è incerta; per riprenderla serve una nuova anteprima.
Visibilità di ricerca AI aperta
Cerca in un'area e recupera altre pagine
In Esplora, scegli una posizione del testo, una preferenza per un punto, un raggio o un riquadro di delimitazione. La preferenza per un punto non è un limite. I risultati con raggio e riquadro escludono le coordinate esterne o non utilizzabili e mantengono i conteggi di esclusione. Le impostazioni dell'area di lavoro controllano il raggio e le dimensioni del riquadro. Dividere un riquadro che attraversa l'antimeridiano; un campione limitato non garantisce una copertura completa.
Le recensioni e i metadati delle foto consentono fino a 100 record per pagina di origine. Al termine di un risultato, aprire Pagine di risultati in Esplora o in Cronologia attività. Esaminare le opzioni di origine esatte, selezionare la casella di controllo di autorizzazione e impostare un budget per recuperare un'altra pagina. L'ordinamento, la lingua, la traduzione e i filtri rimangono fissi per la catena. Cursori mancanti o ripetuti, scadenza, limiti configurati e risultati incerti interrompono la continuazione.
La lettura della pagina successiva del record salvato e l'esportazione di una pagina esistente non richiamano la fonte di dati. Il recupero di una nuova pagina di origine è un'operazione a pagamento separata. Per SDK e agenti, leggere l'anteprima di continuazione, quindi inviare l'attività esatta con una chiave di idempotenza stabile e un budget esplicito. Non pianificare o raggruppare le continuazioni del cursore.
Pianificazione della ricerca all'interno di un poligono
In Esplora, seleziona Confine poligono per una richiesta di rettangolo delimitato con filtro poligono esatto. In Ricerca in blocco, seleziona Confine poligono per importare o incollare un poligono GeoJSON, esaminarne la forma e scegliere righe, colonne e record per cella. Lo schema mostra le coordinate e le celle pianificate; non è una mappa stradale.
Le posizioni utilizzano [longitudine, latitudine]. Chiudi ogni anello ripetendo la sua prima posizione; il primo anello è il bordo esterno e fino a quattro anelli aggiuntivi sono fori esclusi. Autointersezioni, fori toccanti o sovrapposti e attraversamenti antimeridiani vengono rifiutati. Utilizza poligoni separati lungo la linea del cambio di data. Le impostazioni dell'area di lavoro delimitano posizioni, estensione e celle pianificate.
«Rivedi le celle e Budget totale» non chiama la fonte di dati. Esamina ogni cella mantenuta, imposta le unità massime e conferma esplicitamente prima dell'esecuzione. Tutte le attività vengono prenotate insieme oppure nessuna viene ammessa. Le ricevute conservano la versione originale del piano e della configurazione. Il risultato di origine viene filtrato sia in base al poligono che alla cella; i bordi condivisi possono produrre aziende duplicate. Il numero di record richiesto è un limite massimo, non un censimento.
Gli agenti utilizzano preview_polygon_plan seguito da create_reviewed_polygon_batch con lo stesso piano compatto, hash di anteprima, conferma, chiave stabile e budget aggregato. Utilizzare la cronologia batch esistente, i controlli di annullamento e i controlli di risultato/esportazione per attività. I risultati incerti non vengono mai scansionati automaticamente di nuovo.
Esporta la catena di pagine conservate
In Pagine di risultati, apri Esportazione di più pagine e scegli Rivedi l'esportazione della catena completa. Rivedi le pagine attraverso l'attività selezionata, scegli se conservare il primo record per ogni ID esatto o conservare tutti i record, quindi conferma l'esportazione privata. Questa operazione non recupera nuovi dati né utilizza unità di ricerca. Le esportazioni vengono visualizzate anche nei controlli dati con ricevute di avanzamento, annullamento e integrità.
Scarica JSONL, JSON, CSV o Excel al termine dell'operazione. Gli ID duplicati con contenuto modificato vengono conteggiati come conflitti; viene conservato il primo record. I record senza ID rimangono separati. Ogni pagina sorgente ha la propria parte JSONL verificata, in modo che un trasferimento non riuscito possa essere ripreso per parte. Excel supporta fino a 5 MB di parti memorizzate; le esportazioni di dimensioni maggiori utilizzano i formati di streaming. Il formato CSV conserva colonne aggiuntive in extra_fields_json.
Proprietari e amministratori configurano le richieste giornaliere, le esportazioni in sospeso, i limiti di byte per pagina e sorgente e la conservazione nelle Impostazioni. Si applica sempre la data di scadenza della sorgente più vecchia. Conservare il marcatore completo quando si consumano file JSON o JSONL e verificare le parti scaricate rispetto alle relative ricevute SHA-256. Una catena congelata è un campione conservato, non una prova di copertura completa del mercato.
Analizzare i temi delle recensioni con prove citate.
Iniziare con un report sulle statistiche delle recensioni in Report. Un proprietario o un amministratore abilita l'analisi delle recensioni tramite IA in Impostazioni e utilizzo e imposta i tentativi giornalieri, le attività in sospeso, il numero di estratti e la durata della coda. È disattivata per impostazione predefinita.
Selezionare «Analisi dei dati di input» per esaminare gli estratti e il modello esatti prima di autorizzare una generazione. Vengono selezionati fino a 40 estratti distinti e 10 KB di testo degli estratti nell'ordine del report. Una generazione ha i propri limiti di analisi IA e non utilizza unità del portafoglio Maps. I tentativi falliti o annullati vengono comunque conteggiati.
Ogni tema salvato include citazioni verificate e riferimenti alle fonti. I conteggi vengono calcolati a partire da estratti citati distinti, i temi possono sovrapporsi e le interpretazioni dell'IA possono essere errate. Il risultato si apre come report separato: esaminalo in Report, quindi scegli cosa condividere o esportare come PDF. Nascondere la colonna "Estratto della recensione" nasconde anche l'analisi dell'IA.
Gli estratti che non contengono esperienze cliente supportate possono restituire una ricevuta con prove insufficienti senza report. Il tentativo viene comunque conteggiato ai fini dei limiti. Esamina l'input prima di autorizzare un'analisi diversa. Un aggiornamento delle funzionalità interrompe le approvazioni in coda precedenti prima dell'inferenza; esamina una nuova anteprima. I report esistenti mantengono il modello e la versione del prompt originali.
Le ricevute mostrano gli esiti in coda, in esecuzione, completati, falliti, incerti, annullati o scaduti. L'annullamento impedisce la creazione del report, sebbene un'analisi inviata venga comunque conteggiata ai fini dei limiti. Le chiamate incerte non vengono mai riprodotte automaticamente. API, SDK e MCP utilizzano lo stesso hash di anteprima, la stessa conferma esplicita e la stessa chiave di idempotenza.
Preparare e condividere un report per il cliente
Creare un report da una ricerca completata in Report. Modificare il titolo, il marchio, l'ordine delle sezioni e le colonne delle prove, quindi salvare la presentazione. Le Impostazioni dell'area di lavoro forniscono colori del marchio predefiniti, un logo PNG con contorno, il sito Web dell'editore e il piè di pagina. Le presentazioni salvate esistenti vengono modificate solo quando vengono modificate o quando si applicano le impostazioni predefinite dell'area di lavoro.
La condivisione del report revisionato mostra la proiezione pubblica esatta. I campi delle prove non selezionati e le sezioni nascoste vengono esclusi dall'istantanea salvata. Le query di origine sono private per impostazione predefinita; la metodologia rimane visibile. Scegliere chiunque abbia il link o fino a 25 account e-mail verificati, impostare una data di scadenza e confermare esplicitamente il contenuto e il pubblico. È anche possibile richiedere una password di condivisione; inviarla separatamente dal link. I criteri dell'area di lavoro possono richiedere password per ogni condivisione di risultati/report. Non viene inviato alcun invito o e-mail.
Copiare il link completo una sola volta. Il segreto rimane nel suo frammento e non viene conservato nella memoria del browser. Un destinatario con accesso limitato effettua l'accesso con un account verificato autorizzato, quindi apre il link originale e inserisce la password di condivisione quando richiesto. Le password non possono essere recuperate; in caso di smarrimento, è necessario revocarle e crearne una nuova. La scadenza è limitata dalla conservazione della sorgente. I controlli sui dati possono revocare l'accesso futuro; i report eliminati, le sorgenti scadute e gli spazi di lavoro in pausa impediscono nuove letture. Le copie scaricate non possono essere recuperate. JSON e la funzione Stampa/Salva PDF del browser utilizzano la presentazione visualizzata. La funzione Genera PDF salvato crea un file privato dalla versione salvata, con formato carta A4 o Letter e orientamento verticale o orizzontale. I file completati possono essere allegati a una condivisione revisionata corrispondente ed ereditarne le regole di accesso.
Gli agenti utilizzano preview_report_delivery, update_report_presentation, create_reviewed_report_share, list_report_shares e revoke_report_share. La pubblicazione richiede la versione revisionata, l'hash dello snapshot, una chiave di idempotenza stabile e un'autorizzazione esplicita alla condivisione. Il testo di ricerca esterno non può concedere tale autorizzazione.
Genera un PDF conservato
Salva la presentazione, scegli la carta e l'orientamento nelle esportazioni PDF, quindi genera. La cronologia mostra le richieste in coda, in fase di rendering, completate e interrotte con controlli per il download o l'annullamento. Le impostazioni dell'area di lavoro controllano l'abilitazione, le richieste giornaliere, le richieste in sospeso e la conservazione. I file scadono con le loro origini o con il periodo di conservazione configurato, a seconda di quale dei due si verifichi per primo. Il rendering non consuma unità di ricerca. I rendering non riusciti o incerti richiedono una nuova richiesta esplicita; non vengono ritentati automaticamente.
Gli agenti utilizzano generate_report_pdf con la versione revisionata, l'hash dello snapshot, confirmed_render e una chiave di idempotenza. list_report_pdfs e get_report_pdf ispezionano le ricevute; cancel_report_pdf si arresta in attesa della pubblicazione. Genera non crea una condivisione né invia un messaggio. I percorsi di download privato e di allegato del destinatario sono in OpenAPI.
Ricevi le ricevute di lavoro firmate
In Integrazioni, aggiungi un ricevitore HTTPS pubblico. Salva in modo sicuro il suo segreto di firma monouso, abilita la consegna in Impostazioni e Utilizzo, quindi abilita esplicitamente la destinazione. Scegli eventi di lavoro completati, parziali, non riusciti, incerti o annullati. I test inviano solo un flag di test e metadati.
Ogni consegna include due schemi di firma. Per Standard Webhooks, webhook-signature è v1, seguito dall’HMAC-SHA256 in base64 di webhook-id, webhook-timestamp e del corpo grezzo esatto della richiesta, uniti da punti. La chiave è la decodifica base64 del testo che segue whsec_.
Lo schema originale non cambia: X-LocalOS-Signature è v1= seguito dall’HMAC-SHA256 esadecimale di X-LocalOS-Timestamp, un punto e il corpo grezzo, con chiave l’intero segreto whsec_ come testo. Seleziona il segreto con X-LocalOS-Key-ID.
Con entrambi gli schemi, richiedi un timestamp entro cinque minuti e confronta le firme in tempo costante. L’ID evento in webhook-id e X-LocalOS-Event-ID resta uguale tra i tentativi: salvalo in modo univoco prima di applicare effetti e conferma i duplicati con una risposta 2xx.
Verifica una firma Standard Webhooks in Node.js:
// Verifies a webhook delivery with Web Crypto only, so it runs on
// Node.js 20+, Deno, Bun and Cloudflare Workers without dependencies.
// const event = await verifyWebhook({ secret, headers, body });
// secret: "whsec_..." or, while rotating, ["whsec_new", "whsec_old"].
// headers: a Headers object or a plain map such as req.headers.
// body: the raw body as text or bytes, before any JSON parsing.
// Returns the parsed event. Otherwise throws an error whose code says
// why the delivery must not be trusted.
export class WebhookVerificationError extends Error {
constructor(code) {
super(code);
this.name = "WebhookVerificationError";
this.code = code;
}
}
const encoder = new TextEncoder();
function header(headers, name) {
if (typeof headers.get === "function") {
return headers.get(name) ?? undefined;
}
for (const [key, value] of Object.entries(headers)) {
if (key.toLowerCase() === name) {
return Array.isArray(value) ? value[0] : value;
}
}
return undefined;
}
function base64(value) {
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value) || value.length % 4) {
return null;
}
try {
return Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
} catch {
return null;
}
}
// Checks every byte, so the time taken does not reveal a mismatch.
function sameBytes(a, b) {
if (a.length !== b.length) return false;
let difference = 0;
for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
return difference === 0;
}
export async function verifyWebhook({
secret,
headers,
body,
toleranceSeconds = 300,
}) {
if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) {
throw new RangeError("toleranceSeconds must be 0 or more");
}
const fail = (code) => new WebhookVerificationError(code);
const keys = [secret]
.flat()
.map((value) =>
typeof value === "string" ? base64(value.replace(/^whsec_/, "")) : null,
);
if (!keys.length || keys.some((key) => !key?.length)) {
throw fail("WEBHOOK_SECRET_INVALID");
}
const id = header(headers, "webhook-id");
const timestamp = header(headers, "webhook-timestamp");
const signatures = header(headers, "webhook-signature");
if (!id || !timestamp || !signatures) {
throw fail("WEBHOOK_HEADER_MISSING");
}
if (!/^\d{1,12}$/.test(timestamp)) {
throw fail("WEBHOOK_TIMESTAMP_INVALID");
}
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) {
throw fail("WEBHOOK_TIMESTAMP_OUT_OF_RANGE");
}
const subtle = globalThis.crypto?.subtle;
if (!subtle) throw fail("WEBHOOK_CRYPTO_UNAVAILABLE");
const raw =
typeof body === "string" ? encoder.encode(body) : new Uint8Array(body);
const prefix = encoder.encode(`${id}.${timestamp}.`);
const signed = new Uint8Array(prefix.length + raw.length);
signed.set(prefix);
signed.set(raw, prefix.length);
const expected = await Promise.all(
keys.map(async (key) => {
const hmac = await subtle.importKey(
"raw",
key,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
return new Uint8Array(await subtle.sign("HMAC", hmac, signed));
}),
);
// Any listed v1 signature may match; at most 16 are checked.
const verified = signatures
.split(" ")
.filter(Boolean)
.slice(0, 16)
.some((entry) => {
const [version, value] = entry.split(",", 2);
const given = version === "v1" && value ? base64(value) : null;
return given !== null && expected.some((d) => sameBytes(given, d));
});
if (!verified) throw fail("WEBHOOK_SIGNATURE_INVALID");
try {
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(raw));
} catch {
throw fail("WEBHOOK_PAYLOAD_INVALID");
}
}La consegna avviene almeno una volta. Errori di rete, timeout e ogni risposta non 2xx vengono ritentati, compresi i reindirizzamenti, che non vengono mai seguiti. Fino a otto tentativi sono distribuiti su circa 28 ore: dopo 5 secondi, 5 minuti, 30 minuti, 2 ore, 5 ore, poi 10 ore due volte, oppure più tardi se il destinatario invia Retry-After (fino a un giorno). Una risposta 410 Gone mette in pausa la destinazione e annulla le sue consegne in sospeso finché non la riprendi. Tentativi ed errori compaiono in Integrazioni. Mettere in pausa o rimuovere una destinazione annulla le ricevute in sospeso; le richieste già ammesse possono concludersi entro dieci secondi. Le ricevute di consegna salvate non includono record aziendali, termini di ricerca, credenziali dell’account o corpi di risposta del destinatario.
Invia i risultati a Fogli Google, Zapier, Make o n8n
Queste ricette usano solo i webhook firmati descritti sopra e la REST API. Una consegna contiene l’ID dell’attività, lo stato, le unità addebitate e il numero di risultati, non le righe dei risultati. Trattala come un segnale: prendi da essa data.job_id, poi leggi l’attività e le sue righe tramite l’API con una chiave di sola lettura. Queste richieste sono autenticate, quindi le righe provengono sempre dalla tua area di lavoro, anche se qualcuno falsifica una consegna.
Crea quella chiave in API e chiavi con l’accesso «Leggi i risultati esistenti». Può leggere attività ed esportazioni, ma non può avviare ricerche a pagamento. Ogni ricetta usa le funzioni generiche di webhook, codice e richiesta HTTP dello strumento; MapsScraper.dev non ha un’app propria in questi strumenti.
Ogni ricetta esegue due richieste GET con la chiave, inviata come Authorization: Bearer <key>: prima GET /api/v1/jobs/{job_id} e, solo se il suo status è completed o partial, poi GET /api/v1/jobs/{job_id}/export per le righe.
Come funziona la consegna
- Rispondi con qualsiasi stato 2xx entro 10 secondi ed esegui le operazioni lente in seguito.
- I nuovi tentativi riutilizzano la stessa intestazione
webhook-id, quindi salvala e salta le consegne già elaborate. - Una consegna non riuscita viene ritentata, fino a 8 tentativi in circa 28 ore, o più tardi se il ricevitore invia
Retry-After. I reindirizzamenti non vengono seguiti. - Una risposta
410 Gonemette in pausa la destinazione e annulla le sue consegne in sospeso. Correggi il ricevitore, poi riattivala.
n8n
Usa un Webhook node impostato per rispondere subito, un HTTP Request node per ogni richiesta con la chiave in una credenziale Header Auth e, tra i due, un IF node che controlla lo stato. Questa ricetta non verifica la firma; si basa invece sulle richieste autenticate.
Zapier
Il trigger Catch Raw Hook di Zapier trasmette il corpo non analizzato e le intestazioni, quindi questa ricetta verifica anche la firma. Aggiungi un passaggio Code by Zapier che esegue JavaScript e imposta il suo Input Data: body sul corpo grezzo, id, timestamp e signature sulle intestazioni webhook-id, webhook-timestamp e webhook-signature, e secret sul segreto di firma. Chiunque possa modificare lo Zap può vedere quel segreto.
const crypto = require("crypto");
const { body, id, timestamp, signature, secret } = inputData;
const fresh =
/^\d+$/.test(timestamp || "") &&
Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
const key = Buffer.from(
String(secret).replace(/^whsec_/, ""),
"base64",
);
const expected = crypto
.createHmac("sha256", key)
.update(`${id}.${timestamp}.${body}`)
.digest();
const signed = String(signature || "")
.split(" ")
.some((entry) => {
const [version, value = ""] = entry.split(",");
const given = Buffer.from(value, "base64");
return (
version === "v1" &&
given.length === expected.length &&
crypto.timingSafeEqual(given, expected)
);
});
const valid = Boolean(id) && fresh && signed;
if (!valid) return { valid };
return { valid, job_id: JSON.parse(body).data.job_id };Aggiungi un passaggio di filtro che prosegue solo se valid è vero, poi esegui le due richieste con passaggi di richiesta GET. Zapier risponde alla consegna con 200 sia che il controllo riesca sia che no, quindi è il filtro, non la risposta, a bloccare una consegna che non lo supera.
Make
Usa un modulo Custom webhook, che trasforma il corpo JSON in campi, un modulo HTTP per ogni richiesta e, tra i due, un filtro che controlla lo stato. Make risponde da solo alla consegna con 200. Come la ricetta n8n, anche questa non verifica la firma e si basa invece sulle richieste autenticate.
Google Sheets
Sheets recupera le righe invece di riceverle tramite invio. Nel foglio di calcolo, apri Extensions, poi Apps Script, e incolla questo script. In Project Settings, aggiungi una proprietà dello script chiamata MAPSSCRAPER_API_KEY che contiene una chiave API di sola lettura, così la chiave resta fuori dalle celle e dal codice.
const API = "https://mapsscraper.dev/api/v1";
function getJson(path) {
const key = PropertiesService.getScriptProperties()
.getProperty("MAPSSCRAPER_API_KEY");
if (!key) throw new Error("Add the MAPSSCRAPER_API_KEY script property.");
const response = UrlFetchApp.fetch(API + path, {
headers: { Authorization: "Bearer " + key },
muteHttpExceptions: true,
});
const status = response.getResponseCode();
const text = response.getContentText();
if (status !== 200) {
throw new Error("HTTP " + status + ": " + text.slice(0, 200));
}
return JSON.parse(text);
}
// Text that starts with =, +, - or @ gets an apostrophe,
// so Sheets keeps it as text instead of running a formula.
function cell(value) {
if (value === null || value === undefined) return "";
if (typeof value === "object") value = JSON.stringify(value);
const formula = typeof value === "string" && /^[=+\-@]/.test(value);
return formula ? "'" + value : value;
}
function importLatestSearch() {
const jobs = getJson("/jobs?page_size=25&kind=search").items;
const job = jobs.find(
(item) => item.status === "completed" || item.status === "partial",
);
if (!job) return;
const path = "/jobs/" + job.id + "/export?format=json";
const rows = getJson(path).items || [];
if (!rows.length) return;
const columns = [];
rows.forEach((row) => Object.keys(row).forEach((name) => {
if (!columns.includes(name)) columns.push(name);
}));
const values = [columns].concat(
rows.map((row) => columns.map((name) => cell(row[name]))),
);
const book = SpreadsheetApp.getActive();
const sheet =
book.getSheetByName("MapsScraper") || book.insertSheet("MapsScraper");
sheet.clearContents();
sheet.getRange(1, 1, values.length, columns.length).setValues(values);
}Esegui importLatestSearch per copiare la ricerca completata più recente in un foglio chiamato MapsScraper. Per aggiornarlo secondo una pianificazione, aggiungi un attivatore basato sul tempo per importLatestSearch in Triggers. Apps Script interrompe un’esecuzione dopo 6 minuti e non può recuperare una risposta superiore a 50 MB, quindi leggi le attività più grandi pagina per pagina da GET /api/v1/jobs/{job_id}/results.
Limiti importanti
Un sito web mancante significa che la fonte di dati non ne ha fornito uno. Non dimostra che un'azienda non abbia un sito web. Gli snapshot del ranking sono un ordinamento dei risultati di una singola fonte di dati, non una griglia SEO locale completa. La raccolta di recensioni non è un report di sentiment generato dall'IA. Le foto sono metadati, non download di immagini con licenza. Le ricerche ricorrenti conservano gli snapshot, confrontano identità stabili e creano notifiche nella casella di posta dell'area di lavoro. Le ricevute webhook firmate sono facoltative e contengono solo metadati delle attività; la sincronizzazione dei campi specifici del CRM rimane separata.
