En esta página
- Instalar la extensión descomprimida
- Comience con una tarea real
- Volver a escribir el enriquecimiento en una lista guardada
- Inspeccionar la página de inicio de una empresa
- REST API
- Crear un trabajo acotado
- Errores comunes
- Inspeccionar una respuesta de búsqueda de IA
- Buscar un área y obtener más páginas
- Plan de investigación dentro de un polígono
- Exportar la cadena de páginas retenidas
- Analizar temas de reseñas con evidencia citada
- Preparar y compartir un informe de cliente
- Generar un PDF retenido
- Recibir recibos de trabajo firmados
- Enviar resultados a Hojas de cálculo de Google, Zapier, Make o n8n
- Límites importantes
Instalar la extensión descomprimida
La extensión Google Maps scraper se conecta a la API de tu espacio de trabajo. La distribución en las tiendas de Chrome y Edge no está aprobada.
- Descarga y descomprime el paquete de la extensión.
- En Chrome, abre
chrome://extensions. En Edge, abreedge://extensions. Activa el modo de desarrollador, elige «Cargar descomprimida» y selecciona la carpeta descomprimida. - Abre la extensión y elige «Conectar con MapsScraper.dev». Inicia sesión en la página que se abre, donde un propietario o administrador del espacio lo aprueba; después, la extensión recibe su propia clave para esta sesión del navegador. También puedes pegar una clave de API del espacio con el acceso «Leer y ejecutar tareas limitadas» de API y claves.
- Elige tu idioma, usa tu búsqueda actual de Maps, fija los límites de registros y unidades y ejecuta. La búsqueda solo se envía si su estimación cabe en tu límite de unidades.
- Utilice Recuperar envío original cuando se pierda una respuesta. Si la sesión ha finalizado, verifique el Historial de tareas antes de volver a enviarla.
Comience con una tarea real
- Crea un espacio de trabajo y abre Explorar negocios.
- Ingrese una consulta con su ubicación, por ejemplo “Dentistas en Austin, TX”.
- Elige tu límite de registros y filtros opcionales. Los filtros se aplican después de la recuperación.
- Revisar el uso máximo de la unidad. El enriquecimiento aumenta el límite máximo de reserva.
- Ejecute, inspeccione fuentes, seleccione empresas y guárdelas en una lista o exporte CSV.
Volver a escribir el enriquecimiento en una lista guardada
En «Listas guardadas», seleccione los registros que tengan un ID de origen y autorice «Enriquecer seleccionado» con su presupuesto máximo de unidades. «Revisión de reescritura por lotes» abre el nuevo lote; las tareas pendientes permanecen omitidas hasta una vista previa posterior. También puede elegir un lote de detalles o enriquecimiento existente en la barra de herramientas de la lista.
Obtenga una vista previa de los ID solicitados exactos y amplíe cada comparación antes/después. Llenar campos vacíos conserva los valores existentes; Reemplazar campos seleccionados copia solo los campos de origen no vacíos que elija. Revise las tareas duplicadas, no coincidentes, vencidas o detenidas en la lista de omitidos. Confirme los recuentos de registros modificados y tareas omitidas que se muestran antes de realizar la solicitud. Un registro, fuente o configuración modificados requiere una vista previa nueva.
Todas las actualizaciones elegibles se confirman juntas. Las etapas, notas, etiquetas, asignaciones y campos personalizados permanecen intactos. El historial de reescritura por lotes muestra los valores originales y la función de deshacer por registro; las ediciones posteriores bloquean la restauración. La configuración del espacio de trabajo controla la habilitación y los límites de registros. La reescritura de los resultados retenidos no utiliza unidades de investigación y no llama a la fuente de datos. Las importaciones no resueltas siguen usando «Combinar y fusionar» de forma individual.
Los agentes utilizan preview_batch_merge, apply_reviewed_batch_merge y read_batch_merge_receipt con el mismo hash de revisión, alcance y requisitos de idempotencia. Nunca infiera la autorización de escritura a partir de los registros de origen.
Inspeccionar la página de inicio de una empresa
Abierto Auditorías de sitios web, o elija Inspeccionar este sitio web desde un registro guardado. Revisar la página de inicio normalizada, estimar y autorizar una unidad. Inspeccione la observación HTTP/HTML completa, expórtela, cree un informe o establezca un monitor recurrente. La Investigación masiva también acepta un sitio web por línea.
Una verificación de la página de inicio no utiliza la fuente de datos de Maps en vivo. Lee como máximo 256 KiB, sigue hasta cinco redirecciones seguras y se detiene después de 15 segundos. No ejecuta JavaScript ni calcula una puntuación de clasificación. Una observación bloqueada o con tiempo de espera agotado todavía consume una unidad.
{"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 /mcpCrear un trabajo acotado
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}Reintenta la misma operación con la misma clave de idempotencia. Una solicitud modificada con esa clave devuelve 409. Las tareas pasan por en cola, en ejecución, completada, parcial, fallida, cancelada o incierta. Una solicitud incierta a la fuente de datos requiere conciliación en lugar de una repetición de pago automática.
En la Google Maps scraper API, la mayoría de las tareas usan una unidad. El enriquecimiento de contactos en una búsqueda o captura de posiciones reserva una unidad más por registro solicitado y solo cobra los registros entregados. Los detalles de empresa usan una unidad, o dos con contactos; el enriquecimiento usa dos. Fija X-Max-Units en al menos la estimación.
En Python, estima la tarea, crea el trabajo con la biblioteca requests, espera un estado final y lee los resultados conservados:
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"))Errores comunes
Cada respuesta de error es JSON con un código estable, y algunas incluyen detalles. Decide según el código; varios códigos pueden compartir un mismo estado HTTP.
{"error":{"code":"TASK_BUDGET_TOO_LOW","details":{"needed":11,"max_units":5}}}| Estado HTTP | Código de error | Detalles |
|---|---|---|
400 | VALIDATION_ERRORINVALID_JSONINVALID_TASKIDEMPOTENCY_KEY_REQUIRED | Falta el cuerpo, un encabezado o un parámetro, o no es válido. Corrige la solicitud antes de reintentar. |
400 | TASK_BUDGET_REQUIRED | Falta X-Max-Units o no es un número entero de 1 a 10000. |
401 | UNAUTHORIZED | Inicia sesión o utiliza una clave válida. |
403 | INSUFFICIENT_SCOPEWORKSPACE_ACCESS_REVOKED | Su clave o membresía no permite esa acción. |
404 | NOT_FOUND | El recurso no existe en este espacio de trabajo. |
405 | METHOD_NOT_ALLOWED | MCP solo acepta solicitudes POST. GET y DELETE devuelven este código con un encabezado Allow: POST. |
409 | IDEMPOTENCY_CONFLICT | Se reutilizó una clave de idempotencia para una solicitud modificada. |
409 | JOB_NOT_CANCELLABLE | La tarea ya se envió a la fuente de datos o ha terminado, por lo que no se puede cancelar.details.status |
410 | RESULT_EXPIRED | El resultado conservado ha caducado. Ejecuta una nueva tarea para obtenerlo de nuevo. |
410 | WORKSPACE_UNAVAILABLE | El espacio se está borrando. Se rechazan las solicitudes dirigidas a él. |
422 | TASK_BUDGET_TOO_LOW | La estimación supera tu presupuesto de X-Max-Units.details.needed, details.max_units |
422 | INSUFFICIENT_BALANCE | Las unidades disponibles están por debajo del máximo de la tarea.details.needed, details.available |
422 | DAILY_BUDGET_EXCEEDED | La tarea superaría el límite diario de unidades del espacio de trabajo.details.needed, details.daily_limit, details.used_today |
422 | PENDING_LIMIT_REACHED | Ya hay demasiadas tareas en cola o en ejecución.details.pending, details.max_pending |
422 | WORKSPACE_LIMIT_OR_BALANCE_EXCEEDED | Las unidades disponibles o un límite de tareas del espacio no son suficientes. |
423, 409 | WORKSPACE_DELETION_PENDING | El espacio está en su periodo de gracia antes de eliminarse. Los cambios se rechazan con 423, mientras que las lecturas, exportaciones, acciones de cuenta y herramientas MCP de solo lectura siguen funcionando. Un 409 con este código o WORKSPACE_NOT_ACTIVE solo aparece si la eliminación empieza mientras se admite una tarea. |
429 | RATE_LIMITED | Se alcanzó un límite de frecuencia. Espera los segundos indicados en Retry-After y vuelve a intentarlo. |
429 | RESEARCH_DAILY_LIMIT_REACHED | Se agotó el total de investigación en vivo de hoy. Espera los segundos indicados en Retry-After, hasta el siguiente día UTC. No se reserva nada. |
500 | INTERNAL_ERROR | Se produjo un error inesperado. Vuelve a intentarlo más tarde con la misma clave de idempotencia. |
503 | PROVIDER_NOT_CONFIGURED | La investigación en vivo no está abierta a este espacio de trabajo o la fuente de datos de la tarea no está configurada. |
Inspeccionar una respuesta de búsqueda de IA
La visibilidad de la búsqueda de IA conserva una observación de descripción general de la IA de Google: la pregunta, el país, el idioma, el sesgo de ciudad opcional, el texto de la respuesta y los enlaces de citas seguras. Agregue hasta cinco nombres de marcas y dominios, revise el máximo de una unidad y luego autorice explícitamente la tarea. Su administrador debe habilitar el espacio de trabajo y conectar una fuente con licencia.
Inspeccione el historial o exporte evidencia sin otra llamada a la fuente de datos. Compare las líneas de base solo cuando la consulta, la configuración regional y los destinos coincidan. Una visión general faltante significa visibilidad desconocida; el orden de las citas no es el rango de búsqueda. La evidencia incompleta no puede establecer una coincidencia negativa. El seguimiento multimotor no está disponible.
Para seguir un conjunto fijo de preguntas, un propietario o administrador verificado puede programar una serie recurrente: hasta 10 prompts, cada 24 a 720 horas, con un máximo de 30 observaciones en 90 días. Revisa el máximo de unidades de toda la serie y confírmalo de forma explícita. Una serie se pausa cuando cambia su autorización o configuración, o cuando una observación falla o es incierta; para reanudarla se necesita una nueva vista previa.
Visibilidad de búsqueda de IA abierta
Buscar un área y obtener más páginas
En Explorar, elija una ubicación de texto, un sesgo de punto, un radio o un cuadro delimitador. El sesgo puntual no es un límite. Los resultados de radio y cuadro excluyen las coordenadas externas o inutilizables y conservan los recuentos de exclusión. La configuración del espacio de trabajo controla el radio y el tamaño del cuadro. Dividir una caja que cruce el antimeridiano; una muestra limitada no demuestra una cobertura completa.
Las reseñas y los metadatos de fotografías permiten hasta 100 registros por página de origen. Una vez que se complete un resultado, abra Páginas de resultados en Explorar o en el Historial de tareas. Revise las opciones de fuente exactas, marque la casilla de autorización y establezca un presupuesto para recuperar una página más. La clasificación, el idioma, la traducción y los filtros permanecen fijos para la cadena. Los cursores faltantes o repetidos, la caducidad, los límites configurados y los resultados inciertos impiden la continuación.
Leer la siguiente página de registro guardado y exportar una página existente no llama a la fuente de datos. Obtener una nueva página de origen es una operación paga separada. Para SDK y agentes, lea la vista previa de continuación y luego envíe su tarea exacta con una clave de idempotencia estable y un presupuesto explícito. No programe ni agrupe continuaciones del cursor.
Plan de investigación dentro de un polígono
Elija Límite de polígono en Explorar para una solicitud de rectángulo delimitado con filtrado de polígono exacto. En la Investigación masiva, elija Límite de polígono para importar o pegar un polígono GeoJSON, inspeccionar su forma y elegir filas, columnas y registros por celda. El esquema muestra coordenadas y celdas planificadas; No es un mapa de calles.
Uso de posiciones [longitud, latitud]. Cierra cada anillo repitiendo su primera posición; el primer anillo es el borde exterior y se excluyen hasta cuatro anillos adicionales. Se rechazan las autointersecciones, los agujeros que se tocan o se superponen y los cruces de antimeridianos. Utilice polígonos separados a lo largo de la línea de fecha. Configuración del espacio de trabajo, posiciones vinculadas, intervalos y celdas planificadas.
Revisar celdas & presupuesto total no llama a la fuente de datos. Inspeccione cada celda retenida, establezca las unidades máximas y confirme explícitamente antes de ejecutar. Todas las tareas se reservan juntas o no se admite ninguna. Los recibos conservan el plan original y la versión de configuración. Un resultado de origen se filtra tanto por polígono como por celda; los bordes compartidos pueden producir negocios duplicados. El recuento de registros solicitado es un límite máximo, no un censo.
Los agentes utilizan preview_polygon_plan seguido de create_reviewed_polygon_batch con el plan compacto, hash de vista previa, confirmación, clave estable y presupuesto agregado exactos. Utilice el historial de lotes existente, la cancelación y los controles de resultados/exportación por tarea. Los resultados inciertos nunca se vuelven a escanear automáticamente.
Exportar la cadena de páginas retenidas
En Páginas de resultados, abra Exportaciones de varias páginas y elija Revisar exportación de cadena completa. Revise las páginas de la tarea seleccionada, elija si desea conservar el primer registro para cada ID exacta o conservar todos los registros y luego confirme la exportación privada. Esto no recupera nuevos datos ni utiliza unidades de investigación. Las exportaciones también aparecen en Controles de datos con recibos de progreso, cancelación e integridad.
Descargue JSONL, JSON, CSV o Excel una vez finalizado el proceso. Los ID duplicados con contenido modificado se consideran conflictos; se conserva el primer registro. Los registros sin ID permanecen separados. Cada página de origen tiene su propia parte JSONL verificada, de modo que una transferencia fallida puede reanudarse por partes. Excel admite hasta 5 MB de partes almacenadas; las exportaciones de mayor tamaño utilizan los formatos de transmisión. CSV conserva columnas adicionales en extra_fields_json.
Los propietarios y administradores configuran las solicitudes diarias, las exportaciones pendientes, los límites de bytes de origen y de página y la retención en Configuración. Siempre se aplica el vencimiento más temprano de la fuente. Mantenga el marcador completo cuando consuma JSON o JSONL y verifique las partes descargadas con sus recibos SHA-256. Una cadena congelada es una muestra retenida, no una prueba de una cobertura completa del mercado.
Analizar temas de reseñas con evidencia citada
Comience con un informe de estadísticas de reseñas en Informes. Un propietario o administrador habilita el análisis de reseñas con IA en Configuración y uso y establece intentos diarios, tareas pendientes, recuento de extractos y duración de la cola. Está desactivado de forma predeterminada.
Elija Revisar entrada de análisis para inspeccionar los extractos exactos y el modelo antes de autorizar una generación. Se seleccionan hasta 40 extractos distintos y 10 KB de texto de extracto en el orden del informe. Una generación tiene sus propios límites de análisis con IA y no utiliza unidades de billetera de Maps. Los intentos fallidos o cancelados aún cuentan.
Cada tema guardado incluye citas verificadas y referencias de fuentes. Los recuentos se calculan a partir de distintos extractos citados, los temas pueden superponerse y las interpretaciones de la IA pueden ser erróneas. El resultado se abre como un informe separado: examínelo en Informes y luego elija qué compartir o exportar como PDF. Ocultar la columna Extracto de la reseña también oculta el análisis de IA.
Los extractos que no contienen experiencia del cliente respaldada pueden devolver un recibo sin evidencia suficiente sin informe. El intento aún cuenta para los límites. Inspeccione la entrada antes de autorizar un análisis diferente. Una actualización de capacidad detiene las aprobaciones antiguas en cola antes de la inferencia; revisar una nueva vista previa. Los informes existentes conservan su modelo y su versión de prompt originales.
Los recibos muestran resultados en cola, en ejecución, completados, fallidos, inciertos, cancelados o vencidos. La cancelación impide la creación de informes, aunque un análisis enviado aún cuenta para los límites. Las llamadas inciertas nunca se reproducen automáticamente. API, SDK y MCP utilizan el mismo hash de vista previa, confirmación explícita y clave de idempotencia.
Preparar y compartir un informe de cliente
Crear un informe a partir de una investigación completada en Informes. Edite el título, la marca, el orden de las secciones y las columnas de evidencia, luego guarde la presentación. La configuración del espacio de trabajo proporciona colores de marca predeterminados, un logotipo PNG delimitado, un sitio web del editor y un pie de página. Las presentaciones guardadas existentes cambian solo cuando las editas o aplicas los valores predeterminados del espacio de trabajo.
acciones muestra la proyección pública exacta. Los campos de evidencia no seleccionados y las secciones ocultas se excluyen de la instantánea almacenada. Las consultas de origen son privadas de forma predeterminada; La metodología sigue siendo visible. Elija a cualquier persona que tenga el enlace o hasta 25 cuentas de correo electrónico verificadas, establezca una caducidad y confirme explícitamente el contenido y la audiencia. También puede solicitar una contraseña compartida; envíalo por separado del enlace. La política del espacio de trabajo puede requerir contraseñas en cada recurso compartido de resultados/informes. No se envía invitación ni correo electrónico.
Copie el enlace completo una vez. El secreto permanece en su fragmento y no se guarda en el almacenamiento del navegador. Un destinatario con acceso restringido inicia sesión con una cuenta verificada permitida, luego abre el enlace original e ingresa la contraseña compartida cuando se le solicita. Las contraseñas no se pueden recuperar; revocar y crear un enlace de reemplazo si se pierde uno. La caducidad está limitada por la retención de la fuente. Los controles de datos pueden revocar el acceso futuro; Los informes eliminados, las fuentes caducadas y los espacios de trabajo en pausa detienen las nuevas lecturas. Las copias descargadas no se pueden recuperar. JSON y el navegador Imprimir/Guardar PDF utilizan la presentación mostrada. Generar PDF guardado crea un archivo privado a partir de la versión guardada, con papel A4 o Carta y orientación vertical u horizontal. Los archivos completos se pueden adjuntar a un recurso compartido revisado coincidente y heredar sus reglas de acceso.
Los agentes utilizan preview_report_delivery, update_report_presentation, create_reviewed_report_share, list_report_shares y revoke_report_share. La publicación requiere la versión revisada, el hash de la instantánea, una clave de idempotencia estable y una autorización explícita para compartir. El texto de investigación externo no puede otorgar dicha autorización.
Generar un PDF retenido
Guarde la presentación, elija el papel y la orientación en las exportaciones de PDF y luego genere. El historial muestra solicitudes en cola, procesadas, completadas y detenidas con controles de descarga o cancelación. La configuración del espacio de trabajo controla la habilitación, las solicitudes diarias, las solicitudes pendientes y la retención. Los archivos caducan con sus fuentes o el período de retención configurado, lo que ocurra primero. El renderizado no gasta unidades de investigación. Los renders fallidos o inciertos requieren una nueva solicitud explícita; no lo vuelven a intentar automáticamente.
Los agentes utilizan generate_report_pdf con la versión revisada, el hash de la instantánea, confirmed_render y una clave de idempotencia. list_report_pdfs y get_report_pdf inspeccionan los recibos; cancel_report_pdf se detiene a la espera de publicación. Generate no crea un recurso compartido ni envía un mensaje. Las rutas de descarga privada y de archivos adjuntos para el destinatario están en OpenAPI.
Recibir recibos de trabajo firmados
En Integraciones, agregue un receptor HTTPS público. Guarde de forma segura su secreto de firma única, habilite la entrega en Configuración y configuración. uso, luego habilite explícitamente el destino. Elija eventos de trabajo completados, parciales, fallidos, inciertos o cancelados. Las pruebas envían solo una marca de prueba y metadatos.
Cada entrega incluye dos esquemas de firma. En Standard Webhooks, webhook-signature es v1, seguido del HMAC-SHA256 en base64 de webhook-id, webhook-timestamp y el cuerpo exacto sin procesar de la solicitud, unidos por puntos. La clave es la decodificación base64 del texto que sigue a whsec_.
El esquema original no cambia: X-LocalOS-Signature es v1= seguido del HMAC-SHA256 en hexadecimal de X-LocalOS-Timestamp, un punto y el cuerpo sin procesar, usando como clave el secreto whsec_ completo como texto. Selecciona el secreto con X-LocalOS-Key-ID.
Con cualquiera de los dos esquemas, exige una marca de tiempo de menos de cinco minutos y compara las firmas en tiempo constante. El ID de evento de webhook-id y X-LocalOS-Event-ID es el mismo en todos los reintentos: guárdalo de forma única antes de aplicar efectos y responde a los duplicados con un 2xx.
Verifica una firma de Standard Webhooks en 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 entrega es al menos una vez. Se reintentan los fallos de red, los tiempos de espera agotados y toda respuesta que no sea 2xx, incluidas las redirecciones, que nunca se siguen. Hasta ocho intentos se reparten en unas 28 horas: a los 5 segundos, 5 minutos, 30 minutos, 2 horas, 5 horas y luego 10 horas dos veces, o más tarde si el receptor envía Retry-After (hasta un día). Una respuesta 410 Gone pausa el destino y cancela sus entregas pendientes hasta que lo reanudes. Los intentos y errores aparecen en Integraciones. Pausar o eliminar un destino cancela los comprobantes pendientes; las solicitudes ya admitidas pueden terminar en diez segundos. Los comprobantes de entrega guardados no incluyen registros de empresas, términos de búsqueda, credenciales de cuenta ni cuerpos de respuesta del receptor.
Enviar resultados a Hojas de cálculo de Google, Zapier, Make o n8n
Estas recetas usan solo los webhooks firmados descritos arriba y la REST API. Una entrega incluye el ID de la tarea, el estado, las unidades cobradas y el número de resultados, no las filas de resultados. Trátala como una señal: toma de ella data.job_id y luego lee la tarea y sus filas a través de la API con una clave de solo lectura. Esas solicitudes están autenticadas, así que las filas siempre proceden de tu propio espacio de trabajo, aunque alguien falsifique una entrega.
Crea esa clave en API y claves con el acceso «Leer resultados existentes». Puede leer tareas y exportaciones, pero no puede iniciar investigaciones de pago. Cada receta usa las funciones generales de webhook, código y solicitudes HTTP de la herramienta; MapsScraper.dev no tiene una app propia en estas herramientas.
Cada receta hace dos solicitudes GET con la clave, enviada como Authorization: Bearer <key>: primero GET /api/v1/jobs/{job_id} y, solo si su status es completed o partial, después GET /api/v1/jobs/{job_id}/export para las filas.
Cómo funciona la entrega
- Responde con cualquier estado 2xx en un plazo de 10 segundos y haz el trabajo lento después.
- Los reintentos reutilizan el mismo encabezado
webhook-id, así que guárdalo y omite las entregas que ya procesaste. - Una entrega fallida se reintenta, hasta 8 intentos durante unas 28 horas, o más tarde si tu receptor envía
Retry-After. Las redirecciones no se siguen. - Una respuesta
410 Gonepausa el destino y cancela sus entregas pendientes. Corrige el receptor y vuelve a activarlo.
n8n
Usa un Webhook node configurado para responder de inmediato, un HTTP Request node para cada solicitud con la clave en una credencial Header Auth y, entre ellos, un IF node que compruebe el estado. Esta receta no comprueba la firma; en su lugar, se basa en las solicitudes autenticadas.
Zapier
El disparador Catch Raw Hook de Zapier entrega el cuerpo sin analizar junto con los encabezados, así que esta receta también comprueba la firma. Añade un paso Code by Zapier que ejecute JavaScript y configura su Input Data: body con el cuerpo sin procesar, id, timestamp y signature con los encabezados webhook-id, webhook-timestamp y webhook-signature, y secret con el secreto de firma. Cualquiera que pueda editar el Zap puede ver ese secreto.
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 };Añade un paso de filtro que continúe solo si valid es verdadero y luego haz las dos solicitudes con pasos de solicitud GET. Zapier responde a la entrega con 200 tanto si la comprobación se supera como si no, así que es el filtro, no la respuesta, lo que detiene una entrega que no la supera.
Make
Usa un módulo Custom webhook, que convierte el cuerpo JSON en campos, un módulo HTTP para cada solicitud y, entre ellos, un filtro que compruebe el estado. Make responde a la entrega con 200 por sí solo. Como la receta de n8n, esta no comprueba la firma y se basa en las solicitudes autenticadas.
Google Sheets
Sheets extrae las filas en lugar de recibirlas por envío. En tu hoja de cálculo, abre Extensions y luego Apps Script, y pega este script. En Project Settings, añade una propiedad de script llamada MAPSSCRAPER_API_KEY que contenga una clave de API de solo lectura, para que la clave quede fuera de las celdas y del código.
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);
}Ejecuta importLatestSearch para copiar la búsqueda terminada más reciente en una hoja llamada MapsScraper. Para actualizarla de forma programada, añade un activador basado en tiempo para importLatestSearch en Triggers. Apps Script detiene una ejecución tras 6 minutos y no puede obtener una respuesta de más de 50 MB, así que lee las tareas más grandes página por página desde GET /api/v1/jobs/{job_id}/results.
Límites importantes
El sitio web que falta significa que la fuente de datos no proporcionó ninguno. No prueba que una empresa no tenga un sitio web. Las instantáneas de clasificación son un orden de resultados de una fuente de datos, no una cuadrícula de SEO local completa. La recopilación de reseñas no es un informe de opinión generado por IA. Las fotos son metadatos, no descargas de imágenes con licencia. Las búsquedas recurrentes conservan instantáneas, comparan identidades estables y crean notificaciones en la bandeja de entrada del espacio de trabajo. Los recibos de webhook firmados son opcionales y contienen metadatos de tareas únicamente; la sincronización de campos específica de CRM permanece separada.
