Documentatie voor Google Maps-scrapers. Van zoekopdracht tot export.

Van uw eerste zoekopdracht tot een herhaalbare workflow voor agenten. Vind hier de instructies, beperkingen en API-contracten.

Op deze pagina
  1. Installeer de uitgepakte extensie
  2. Begin met één echte taak.
  3. Verrijking terugschrijven naar een opgeslagen lijst.
  4. Inspecteer een bedrijfshomepage
  5. REST API
  6. Een beperkte taak maken
  7. Veelvoorkomende fouten
  8. Controleer een AI-zoekresultaat
  9. Zoek in een gebied en haal meer pagina's op
  10. Plan onderzoek binnen een polygoon
  11. Exporteer de bewaarde paginaketen
  12. Analyseer reviewthema's met aangehaald bewijsmateriaal.
  13. Een klantrapport voorbereiden en delen
  14. Genereer een bewaard PDF-bestand.
  15. Ontvang ondertekende taakontvangstbewijzen
  16. Resultaten naar Google Spreadsheets, Zapier, Make of n8n sturen
  17. Belangrijke grenzen

Installeer de uitgepakte extensie

De Google Maps scraper-extensie maakt verbinding met de API van uw werkruimte. Distributie via de Chrome- en Edge-stores is niet goedgekeurd.

  1. Download en unzip het extensiepakket.
  2. Open in Chrome chrome://extensions. Open in Edge edge://extensions. Schakel de ontwikkelaarsmodus in, kies 'Uitgepakt laden' en selecteer de uitgepakte map.
  3. Open de extensie en kies ‘Verbinden met MapsScraper.dev’. Meld u aan op de pagina die opent, waar een eigenaar of beheerder van de werkruimte goedkeurt; de extensie krijgt daarna een eigen sleutel voor deze browsersessie. U kunt ook een werkruimte-API-sleutel met de toegang ‘Beperkte taken lezen en uitvoeren’ uit API en sleutels plakken.
  4. Kies uw taal, gebruik uw huidige Maps-zoekopdracht, stel de record- en eenhedenlimiet in en start. De zoekopdracht wordt alleen verzonden als de raming binnen uw eenhedenlimiet past.
  5. Gebruik 'Oorspronkelijke inzending herstellen' wanneer een reactie verloren gaat. Als de sessie is beëindigd, controleer dan de taakgeschiedenis voordat u opnieuw indient.
Download het uitgepakte extensie-ZIP-bestand.
Het OpenAPI 3.1-contract documenteert alle geïmplementeerde werkruimteroutes, paginering, expliciete budgetten en reactieschema's. Verken de functiegids voor elke werkruimtepagina.

Begin met één echte taak.

  1. Maak een werkruimte en open Bedrijven verkennen.
  2. Voer een zoekopdracht in met de bijbehorende locatie, bijvoorbeeld 'Tandartsen in Austin, TX'.
  3. Kies uw recordlimiet en optionele filters. Filters worden toegepast na het ophalen.
  4. Controleer het maximale eenheidsgebruik. Verrijking verhoogt het gereserveerde plafond.
  5. Uitvoeren, bronnen inspecteren, bedrijven selecteren en opslaan in een lijst of exporteren als CSV.

Verrijking terugschrijven naar een opgeslagen lijst.

Selecteer in 'Opgeslagen lijsten' records met een bron-ID en autoriseer 'Geselecteerde gegevens verrijken' met het maximale eenheidsbudget. 'Batchterugschrijven controleren' opent de nieuwe batch; taken in behandeling blijven overgeslagen tot een latere preview. U kunt ook een bestaande detail- of verrijkingsbatch selecteren in de lijstwerkbalk.

Bekijk de exact aangevraagde ID's en vouw elke voor/na-vergelijking uit. 'Lege velden invullen' behoudt bestaande waarden; 'Geselecteerde velden vervangen' kopieert alleen de niet-lege bronvelden die u kiest. Controleer dubbele, niet-overeenkomende, verlopen of gestopte taken in de overgeslagen lijst. Bevestig het weergegeven aantal gewijzigde records en overgeslagen taken voordat u de wijzigingen toepast. Een gewijzigd record, bron of configuratie vereist een nieuwe preview.

Alle in aanmerking komende updates worden samen doorgevoerd. Fasen, notities, tags, toewijzingen en aangepaste velden blijven intact. De geschiedenis van het terugschrijven in batches toont de oorspronkelijke waarden en de mogelijkheid om per record ongedaan te maken; latere bewerkingen blokkeren herstel. Werkruimte-instellingen bepalen de inschakeling en het aantal records. Het terugschrijven van bewaarde resultaten gebruikt geen onderzoekseenheden en roept de gegevensbron niet aan. Onopgeloste importen gebruiken nog steeds afzonderlijk 'Overeenkomen en samenvoegen'.

Agenten gebruiken preview_batch_merge, apply_reviewed_batch_merge en read_batch_merge_receipt met dezelfde review-hash, scope en idempotentievereisten. Leid nooit schrijfautorisatie af uit bronrecords.

Inspecteer een bedrijfshomepage

Openen Websiteauditsof kies 'Deze website inspecteren' vanuit een opgeslagen record. Bekijk de genormaliseerde homepage, schat en autoriseer één eenheid. Inspecteer de voltooide HTTP/HTML-observatie, exporteer deze, maak een rapport of stel een terugkerende monitor in. Bulkonderzoek accepteert ook één website per regel.

Een homepagecontrole gebruikt de live Maps-gegevensbron niet. Deze leest maximaal 256 KiB, volgt maximaal vijf veilige omleidingen en stopt na 15 seconden. Er wordt geen JavaScript uitgevoerd en er wordt geen rankingscore berekend. Een geblokkeerde of verlopen observatie verbruikt nog steeds één eenheid.

{"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     /mcp

Een beperkte taak maken

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}

Probeer dezelfde bewerking opnieuw met dezelfde idempotentiesleutel. Een gewijzigd verzoek met die sleutel geeft 409. Taken doorlopen in wachtrij, actief, voltooid, gedeeltelijk, mislukt, geannuleerd of onzeker. Een onzeker verzoek aan de gegevensbron vereist afstemming in plaats van een automatische betaalde herhaling.

De meeste taken gebruiken één eenheid. Contactverrijking bij een zoekopdracht of rangschikkingsmomentopname reserveert één extra eenheid per gevraagd record en rekent alleen geleverde records af. Bedrijfsgegevens gebruiken één eenheid, of twee met contacten; verrijking gebruikt er twee. Stel X-Max-Units minstens in op de raming.

Maak in Python een schatting voor de taak, maak de job aan met de requests-bibliotheek, wacht op een eindstatus en lees de bewaarde resultaten:

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"))

Veelvoorkomende fouten

Elke foutreactie is JSON met een stabiele code, en sommige bevatten details. Baseer uw logica op de code; meerdere codes kunnen dezelfde HTTP-status hebben.

{"error":{"code":"TASK_BUDGET_TOO_LOW","details":{"needed":11,"max_units":5}}}
HTTP-statusFoutcodeDetails
400VALIDATION_ERRORINVALID_JSONINVALID_TASKIDEMPOTENCY_KEY_REQUIREDDe body, een header of een parameter ontbreekt of is ongeldig. Corrigeer het verzoek voordat u het opnieuw probeert.
400TASK_BUDGET_REQUIREDX-Max-Units ontbreekt of is geen geheel getal van 1 tot en met 10000.
401UNAUTHORIZEDAanmelden of een geldige sleutel gebruiken.
403INSUFFICIENT_SCOPEWORKSPACE_ACCESS_REVOKEDUw sleutel of lidmaatschap staat deze actie niet toe.
404NOT_FOUNDDe resource bestaat niet in deze werkruimte.
405METHOD_NOT_ALLOWEDMCP accepteert alleen POST-verzoeken. GET en DELETE geven deze code terug met een Allow: POST-header.
409IDEMPOTENCY_CONFLICTEen idempotentiesleutel is hergebruikt voor een gewijzigd verzoek.
409JOB_NOT_CANCELLABLEDe taak is al naar de gegevensbron verzonden of is voltooid en kan dus niet worden geannuleerd.
details.status
410RESULT_EXPIREDHet bewaarde resultaat is verlopen. Voer een nieuwe taak uit om het opnieuw op te halen.
410WORKSPACE_UNAVAILABLEDe werkruimte wordt gewist. Verzoeken aan deze werkruimte worden geweigerd.
422TASK_BUDGET_TOO_LOWDe raming is hoger dan uw budget in X-Max-Units.
details.needed, details.max_units
422INSUFFICIENT_BALANCEDe beschikbare eenheden liggen onder het maximum van de taak.
details.needed, details.available
422DAILY_BUDGET_EXCEEDEDDe taak zou de dagelijkse eenhedenlimiet van de werkruimte overschrijden.
details.needed, details.daily_limit, details.used_today
422PENDING_LIMIT_REACHEDEr staan al te veel taken in de wachtrij of zijn actief.
details.pending, details.max_pending
422WORKSPACE_LIMIT_OR_BALANCE_EXCEEDEDDe beschikbare eenheden of een taaklimiet van de werkruimte zijn onvoldoende.
423, 409WORKSPACE_DELETION_PENDINGDe werkruimte zit in de respijtperiode vóór verwijdering. Wijzigingen worden geweigerd met 423, terwijl lezen, exports, accountacties en alleen-lezen MCP-tools blijven werken. Een 409 met deze code of WORKSPACE_NOT_ACTIVE verschijnt alleen als de verwijdering begint terwijl een taak wordt toegelaten.
429RATE_LIMITEDEr is een snelheidslimiet bereikt. Wacht het aantal seconden uit Retry-After en probeer het dan opnieuw.
429RESEARCH_DAILY_LIMIT_REACHEDHet live-onderzoekstotaal van vandaag is op. Wacht het aantal seconden uit Retry-After, tot de volgende UTC-dag. Er wordt niets gereserveerd.
500INTERNAL_ERROREr is een onverwachte fout opgetreden. Probeer het later opnieuw met dezelfde idempotentiesleutel.
503PROVIDER_NOT_CONFIGUREDLive-onderzoek is niet opengesteld voor deze werkruimte, of de gegevensbron van de taak is niet geconfigureerd.

Controleer een AI-zoekresultaat

De zichtbaarheid van AI-zoekresultaten behoudt één Google AI-overzichtsobservatie: de vraag, het land, de taal, de optionele stadsvoorkeur, de antwoordtekst en veilige citatielinks. Voeg maximaal vijf merknamen en domeinen toe, controleer het maximum van één eenheid en autoriseer de taak vervolgens expliciet. Uw beheerder moet de werkruimte inschakelen en een gelicentieerde bron koppelen.

Bekijk de geschiedenis of exporteer bewijsmateriaal zonder een nieuwe aanroep van de gegevensbron. Vergelijk basislijnen alleen wanneer de zoekopdracht, de locatie en de doelen overeenkomen. Een ontbrekend overzicht betekent onbekende zichtbaarheid; de citatievolgorde is niet de zoekrang. Onvolledig bewijsmateriaal kan geen negatieve match vaststellen. Tracking van meerdere zoekmachines is niet beschikbaar.

Om een vaste set vragen te volgen, kan een geverifieerde eigenaar of beheerder een terugkerende reeks plannen: maximaal 10 prompts, elke 24 tot 720 uur, voor maximaal 30 waarnemingen binnen 90 dagen. Controleer de maximale eenheden voor de hele reeks en bevestig die uitdrukkelijk. Een reeks pauzeert wanneer de autorisatie of configuratie verandert, of wanneer een waarneming mislukt of onzeker is; hervatten vereist een nieuw voorbeeld.

Open de zichtbaarheid van AI-zoekresultaten

Zoek in een gebied en haal meer pagina's op

Kies in Verkennen een tekstlocatie, een puntvoorkeur, een straal of een begrenzingskader. Puntvoorkeur is geen grens. Resultaten met een straal en een kader sluiten coördinaten buiten of onbruikbaar uit en behouden de uitsluitingsaantallen. Werkruimte-instellingen bepalen de grootte van de straal en het kader. Splits een vak dat de antimeridiaan kruist; een begrensde steekproef bewijst geen volledige dekking.

Reviews en fotometadata staan maximaal 100 records per bronpagina toe. Nadat een resultaat is voltooid, opent u Resultaatpagina’s in Verkennen of Taakgeschiedenis. Controleer de exacte bronopties, vink het autorisatievakje aan en stel een budget in om nog één pagina op te halen. Sortering, taal, vertaling en filters blijven vast voor de keten. Ontbrekende of herhaalde cursors, vervaldatum, geconfigureerde limieten en onzekere uitkomsten stoppen de voortzetting.

Het lezen van de volgende opgeslagen recordpagina en het exporteren van een bestaande pagina roepen de gegevensbron niet aan. Het ophalen van een nieuwe bronpagina is een aparte betaalde bewerking. Lees voor SDK's en agents de voortzettingspreview en dien vervolgens de exacte taak in met een stabiele idempotentiesleutel en een expliciet budget. Plan of batch geen cursorvoortzettingen.

Plan onderzoek binnen een polygoon

Kies Polygoonbegrenzing in Verkennen voor één verzoek met een begrensde rechthoek en exacte polygoonfiltering. Kies in Bulkonderzoek Polygoonbegrenzing om een GeoJSON-polygoon te importeren of te plakken, de vorm ervan te inspecteren en rijen, kolommen en records per cel te kiezen. Het schema toont coördinaten en geplande cellen; het is geen stratenkaart.

Posities gebruiken [lengtegraad, breedtegraad]. Sluit elke ring door de eerste positie te herhalen; de eerste ring is de buitenrand en maximaal vier extra ringen zijn uitgesloten gaten. Zelfdoorsnijdingen, aan elkaar grenzende of overlappende gaten en kruisingen met de antimeridiaan worden afgewezen. Gebruik afzonderlijke polygonen over de datumgrens. Werkruimte-instellingen begrenzen posities, bereik en geplande cellen.

Cellen en totaalbudget controleren roept de gegevensbron niet aan. Inspecteer elke behouden cel, stel de maximale eenheden in en bevestig expliciet voordat u de uitvoering start. Alle taken worden samen gereserveerd of geen enkele wordt geaccepteerd. Ontvangsten behouden de oorspronkelijke plan- en configuratieversie. Een bronresultaat wordt gefilterd op basis van zowel polygoon als cel; gedeelde randen kunnen leiden tot dubbele bedrijven. Het gevraagde aantal records is een maximum, geen telling.

Agenten gebruiken preview_polygon_plan gevolgd door create_reviewed_polygon_batch met exact hetzelfde compacte plan, preview-hash, bevestiging, stabiele sleutel en geaggregeerd budget. Gebruik de bestaande batchgeschiedenis, annulerings- en per-taak resultaat-/exportcontroles. Onzekere resultaten worden nooit automatisch opnieuw gescand.

Exporteer de bewaarde paginaketen

Open in Resultaatpagina’s 'Exporteren van meerdere pagina's' en kies 'Export van de volledige keten beoordelen'. Bekijk de pagina's via de geselecteerde taak, kies of u het eerste record voor elke exacte ID wilt behouden of alle records wilt behouden en bevestig vervolgens de privé-export. Dit haalt geen nieuwe gegevens op en gebruikt geen onderzoekseenheden. Exports verschijnen ook in Gegevensbeheer met voortgangs-, annulerings- en integriteitsbevestigingen.

Download JSONL, JSON, CSV of Excel na voltooiing. Dubbele ID's met gewijzigde inhoud worden als conflicten beschouwd; de eerste record wordt behouden. Records zonder ID's blijven gescheiden. Elke bronpagina heeft zijn eigen geverifieerde JSONL-deel, zodat een mislukte overdracht per deel kan worden hervat. Excel ondersteunt tot 5 MB aan opgeslagen delen; grotere exports gebruiken de streamingformaten. CSV behoudt extra kolommen in extra_fields_json.

Eigenaren en beheerders configureren dagelijkse verzoeken, openstaande exports, pagina- en bronbyte-limieten en bewaartermijnen in Instellingen. De vroegste vervaldatum van de bron is altijd van toepassing. Bewaar de volledige marker bij het consumeren van JSON of JSONL en verifieer gedownloade delen aan de hand van hun SHA-256-ontvangstbewijzen. Een bevroren keten is een bewaard voorbeeld, geen bewijs van volledige marktdekking.

Analyseer reviewthema's met aangehaald bewijsmateriaal.

Begin met een rapport met reviewstatistieken in Rapporten. Een eigenaar of beheerder schakelt AI-reviewanalyse in in Instellingen & gebruik en stelt dagelijkse pogingen, openstaande taken, aantal fragmenten en levensduur van de wachtrij in. Deze functie is standaard uitgeschakeld.

Kies 'Analyse-input beoordelen' om de exacte fragmenten en het model te inspecteren voordat een generatie wordt geautoriseerd. Maximaal 40 verschillende fragmenten en 10 KB aan fragmenttekst worden geselecteerd in de rapportvolgorde. Een generatie heeft eigen limieten voor AI-analyse en gebruikt geen Maps-wallet-eenheden. Mislukte of geannuleerde pogingen tellen nog steeds mee.

Elk opgeslagen thema bevat gecontroleerde citaten en bronverwijzingen. Het aantal wordt berekend op basis van afzonderlijke geciteerde fragmenten, thema's kunnen overlappen en AI-interpretaties kunnen onjuist zijn. Het resultaat wordt geopend als een apart rapport: bekijk het in Rapporten en kies vervolgens wat u wilt delen of exporteren als PDF. Het verbergen van de kolom 'Reviewfragment' verbergt ook de AI-analyse.

Fragmenten die geen ondersteunde klantervaring bevatten, kunnen een ontvangstbewijs met onvoldoende bewijs opleveren zonder rapport. De poging telt nog steeds mee voor de limieten. Controleer de invoer voordat u een andere analyse autoriseert. Een upgrade van de functionaliteit stopt oudere goedkeuringen in de wachtrij vóór de inferentie; bekijk een nieuwe preview. Bestaande rapporten behouden hun oorspronkelijke model- en promptversie.

Ontvangsten tonen resultaten in de wachtrij, actief, voltooid, mislukt, onzeker, geannuleerd of verlopen. Annulering voorkomt het aanmaken van een rapport, hoewel een verzonden analyse nog steeds meetelt voor de limieten. Onzekere oproepen worden nooit automatisch opnieuw afgespeeld. API, SDK en MCP gebruiken dezelfde preview-hash, expliciete bevestiging en idempotentiesleutel.

Een klantrapport voorbereiden en delen

Een rapport maken van voltooid onderzoek in Rapporten. Bewerk de titel, het merk, de volgorde van de secties en de kolommen met bewijsmateriaal en sla de presentatie vervolgens op. Werkruimte-instellingen bieden standaard merkkleuren, een ingekaderd PNG-logo, de website van de uitgever en een voettekst. Bestaande opgeslagen presentaties worden alleen gewijzigd wanneer u ze bewerkt of de standaardinstellingen van de werkruimte toepast.

Het gedeelde beoordeelde rapport toont de exacte openbare projectie. Niet-geselecteerde bewijsvelden en verborgen secties worden uitgesloten van de opgeslagen momentopname. Bronquery's zijn standaard privé; de methodologie blijft zichtbaar. Kies iedereen met de link of maximaal 25 geverifieerde e-mailaccounts, stel een vervaldatum in en bevestig expliciet de inhoud en het publiek. U kunt ook een wachtwoord voor delen vereisen; verstuur dit apart van de link. Het werkruimtebeleid kan wachtwoorden vereisen voor elke gedeelde resultaat/rapport. Er wordt geen uitnodiging of e-mail verzonden.

De volledige link eenmaal kopiëren. Het geheim blijft in het fragment en wordt niet opgeslagen in de browser. Een ontvanger met beperkte toegang meldt zich aan met een toegestaan, geverifieerd account, opent vervolgens de oorspronkelijke link en voert het wachtwoord voor de gedeelde map in wanneer daarom wordt gevraagd. Wachtwoorden kunnen niet worden hersteld; trek de link in en maak een vervangende link aan als deze verloren gaat. De geldigheidsduur is beperkt door de bewaartermijn van de bron. Gegevensbeheer kan toekomstige toegang intrekken; verwijderde rapporten, verlopen bronnen en gepauzeerde werkruimten voorkomen nieuwe leesbewerkingen. Gedownloade kopieën kunnen niet worden teruggehaald. JSON en de browserfuncties Afdrukken/PDF opslaan gebruiken de weergegeven presentatie. Het genereren van een opgeslagen PDF maakt een privébestand van de opgeslagen versie, op A4- of Letter-papier en in portret- of landschapsoriëntatie. Voltooide bestanden kunnen worden gekoppeld aan een overeenkomende, beoordeelde gedeelde map en erven de toegangsregels daarvan.

Agenten gebruiken preview_report_delivery, update_report_presentation, create_reviewed_report_share, list_report_shares en revoke_report_share. Publicatie vereist de beoordeelde versie, snapshot-hash, een stabiele idempotentiesleutel en expliciete toestemming voor delen. Externe onderzoeksteksten kunnen die toestemming niet verlenen.

Genereer een bewaard PDF-bestand.

Sla de presentatie op, kies papier en oriëntatie bij het exporteren naar PDF en genereer vervolgens. De geschiedenis toont in de wachtrij staande, renderende, voltooide en gestopte verzoeken met download- of annuleringsopties. Werkruimte-instellingen regelen de inschakeling, dagelijkse verzoeken, openstaande verzoeken en bewaartermijn. Bestanden verlopen samen met hun bronnen of na de geconfigureerde bewaartermijn, afhankelijk van wat zich het eerst voordoet. Renderen verbruikt geen onderzoekseenheden. Mislukte of onzekere renders vereisen een nieuw expliciet verzoek; ze worden niet automatisch opnieuw geprobeerd.

gebruiken generate_report_pdf met de gecontroleerde versie, snapshot-hash, confirmed_render en een idempotentiesleutel. list_report_pdfs en get_report_pdf controleren ontvangsten; cancel_report_pdf stopt publicatie in afwachting. Genereren creëert geen gedeelde map en verzendt geen bericht. Routes voor privédownloads en bijlagen voor ontvangers zijn beschikbaar in OpenAPI.

Ontvang ondertekende taakontvangstbewijzen

Voeg in Integraties een openbare HTTPS-ontvanger toe. Sla het eenmalige ondertekeningsgeheim veilig op en schakel levering in in Instellingen & Gebruik de bestemming en schakel deze vervolgens expliciet in. Kies voltooide, gedeeltelijke, mislukte, onzekere of geannuleerde taakgebeurtenissen. Tests verzenden alleen een testvlag en metadata.

Elke bezorging bevat twee handtekeningschema’s. Voor Standard Webhooks is webhook-signature v1, gevolgd door de base64-HMAC-SHA256 van webhook-id, webhook-timestamp en de exacte onbewerkte verzoekbody, verbonden door punten. De sleutel is de base64-decodering van de tekst na whsec_.

Het oorspronkelijke schema blijft ongewijzigd: X-LocalOS-Signature is v1= gevolgd door de hexadecimale HMAC-SHA256 van X-LocalOS-Timestamp, een punt en de onbewerkte body, met het volledige whsec_-geheim als tekst als sleutel. Kies het geheim met X-LocalOS-Key-ID.

Eis bij beide schema’s een tijdstempel binnen vijf minuten en vergelijk handtekeningen in constante tijd. De gebeurtenis-ID in webhook-id en X-LocalOS-Event-ID blijft bij herhalingen gelijk: sla die uniek op voordat u effecten toepast en bevestig duplicaten met een 2xx-antwoord.

Een Standard Webhooks-handtekening verifiëren 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");
  }
}

Bezorging gebeurt minstens één keer. Netwerkfouten, time-outs en elk antwoord buiten 2xx worden opnieuw geprobeerd, ook omleidingen, die nooit worden gevolgd. Maximaal acht pogingen worden verspreid over ongeveer 28 uur: na 5 seconden, 5 minuten, 30 minuten, 2 uur, 5 uur en daarna twee keer 10 uur, of later als de ontvanger Retry-After stuurt (tot één dag). Een 410 Gone-antwoord pauzeert de bestemming en annuleert de openstaande bezorgingen tot u die hervat. Pogingen en fouten verschijnen in Integraties. Een bestemming pauzeren of verwijderen annuleert openstaande ontvangstbewijzen; verzoeken die al zijn toegelaten, kunnen binnen tien seconden worden afgerond. Opgeslagen bezorgbewijzen bevatten geen bedrijfsrecords, zoektermen, accountgegevens of antwoordinhoud van de ontvanger.

Resultaten naar Google Spreadsheets, Zapier, Make of n8n sturen

Deze recepten gebruiken alleen de ondertekende webhooks hierboven en de REST API. Een levering bevat de taak-ID, status, in rekening gebrachte eenheden en het aantal resultaten, niet de resultaatrijen. Behandel haar als een signaal: neem er data.job_id uit en lees daarna de taak en de rijen via de API met een alleen-lezen sleutel. Die verzoeken zijn geauthenticeerd, dus de rijen komen altijd uit je eigen werkruimte, ook als iemand een levering vervalst.

Maak die sleutel aan in API & sleutels met de toegang Bestaande resultaten lezen. Die sleutel kan taken en exports lezen, maar geen betaald onderzoek starten. Elk recept gebruikt de algemene webhook-, code- en HTTP-requestfuncties van de tool; MapsScraper.dev heeft in deze tools geen eigen app.

Elk recept doet twee GET-verzoeken met de sleutel, verzonden als Authorization: Bearer <key>: eerst GET /api/v1/jobs/{job_id} en alleen als de status daarvan completed of partial is, daarna GET /api/v1/jobs/{job_id}/export voor de rijen.

Hoe levering werkt

  • Antwoord binnen 10 seconden met een willekeurige 2xx-status en doe traag werk daarna.
  • Nieuwe pogingen gebruiken dezelfde header webhook-id, dus sla die op en sla leveringen over die je al hebt verwerkt.
  • Een mislukte levering wordt opnieuw geprobeerd, tot 8 pogingen in ongeveer 28 uur, of later als je ontvanger Retry-After stuurt. Omleidingen worden niet gevolgd.
  • Een antwoord 410 Gone pauzeert de bestemming en annuleert de wachtende leveringen. Herstel de ontvanger en zet de bestemming daarna weer aan.

n8n

Gebruik een Webhook node die direct antwoordt, een HTTP Request node per verzoek met de sleutel in Header Auth-inloggegevens, en daartussen een IF node die de status controleert. Dit recept controleert de handtekening niet, maar vertrouwt in plaats daarvan op de geauthenticeerde verzoeken.

Zapier

De trigger Catch Raw Hook van Zapier geeft de ongeparste body en de headers door, dus dit recept controleert ook de handtekening. Voeg een stap Code by Zapier toe die JavaScript uitvoert en stel de Input Data in: body op de ruwe body, id, timestamp en signature op de headers webhook-id, webhook-timestamp en webhook-signature, en secret op het ondertekeningsgeheim. Iedereen die de Zap kan bewerken, kan dat geheim zien.

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 };

Voeg een filterstap toe die alleen doorgaat als valid true is, en doe daarna de twee verzoeken met GET-requeststappen. Zapier beantwoordt de levering met 200, of de controle nu slaagt of niet, dus het filter, niet het antwoord, houdt een levering tegen die niet slaagt.

Make

Gebruik een module Custom webhook, die de JSON-body in velden omzet, een HTTP-module per verzoek en daartussen een filter dat de status controleert. Make beantwoordt de levering zelf met 200. Net als het n8n-recept controleert dit recept de handtekening niet, maar vertrouwt het op de geauthenticeerde verzoeken.

Google Sheets

Google Sheets haalt de rijen zelf op in plaats van een push te ontvangen. Open in uw spreadsheet Extensions en daarna Apps Script, en plak dit script. Voeg onder Project Settings een scripteigenschap met de naam MAPSSCRAPER_API_KEY toe die een alleen-lezen API-sleutel bevat, zodat de sleutel buiten de cellen en de code blijft.

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);
}

Voer de functie importLatestSearch uit om de nieuwste voltooide zoekopdracht te kopiëren naar een blad met de naam MapsScraper. Om dit volgens een schema te vernieuwen, voegt u onder Triggers een tijdgestuurde trigger voor importLatestSearch toe. Apps Script stopt een uitvoering na 6 minuten en kan geen antwoord groter dan 50 MB ophalen, dus lees grotere taken pagina voor pagina via GET /api/v1/jobs/{job_id}/results.

Belangrijke grenzen

Een ontbrekende website betekent dat de gegevensbron er geen heeft aangeleverd. Het bewijst niet dat een bedrijf geen website heeft. Rank snapshots zijn een rangschikking van de resultaten van één gegevensbron, geen volledig lokaal SEO-raster. Het verzamelen van reviews is geen door AI gegenereerd sentimentrapport. Foto's zijn metadata, geen gelicentieerde afbeeldingen om te downloaden. Terugkerende zoekopdrachten bewaren snapshots, vergelijken stabiele identiteiten en genereren meldingen in de werkruimte-inbox. Ondertekende webhook-ontvangsten zijn opt-in en bevatten alleen taakmetadata; CRM-specifieke veldsynchronisatie blijft gescheiden.