Nesta página
- Instale a extensão descompactada
- Comece com uma tarefa real.
- Grave o enriquecimento de volta em uma lista salva.
- Inspecione a página inicial de uma empresa
- REST API
- Criar um trabalho limitado
- Erros comuns
- Inspecionar uma resposta de pesquisa de IA
- Pesquise uma área e busque mais páginas
- Planejar pesquisa dentro de um polígono
- Exporte a cadeia de páginas retida.
- Analise temas de avaliação com evidências citadas
- Preparar e compartilhar um relatório do cliente
- Gerar um PDF retido
- Receba recibos de trabalho assinados.
- Enviar resultados para Google Planilhas, Zapier, Make ou n8n
- Limites importantes
Instale a extensão descompactada
A extensão Google Maps scraper se conecta à API do seu espaço de trabalho. A distribuição nas lojas do Chrome e do Edge não foi aprovada.
- Baixe e descompacte o pacote da extensão.
- No Chrome, abra
chrome://extensions. No Edge, abraedge://extensions. Ative o Modo de desenvolvedor, escolha Carregar descompactado e selecione a pasta descompactada. - Abra a extensão e escolha “Conectar com MapsScraper.dev”. Faça login na página que abrir, onde um proprietário ou administrador do espaço aprova; depois, a extensão recebe a própria chave para esta sessão do navegador. Você também pode colar uma chave de API do espaço com o acesso “Ler e executar tarefas limitadas” de API e chaves.
- Escolha o idioma, use sua busca atual do Maps, defina os limites de registros e unidades e execute. A busca só é enviada quando a estimativa cabe no seu limite de unidades.
- Use a opção Recuperar envio original quando uma resposta for perdida. Se a sessão tiver sido encerrada, verifique o histórico da tarefa antes de reenviar.
Comece com uma tarefa real.
- Crie um espaço de trabalho e abra Explorar empresas.
- Insira uma consulta com sua localização, por exemplo, “Dentistas em Austin, TX”.
- Escolha seu limite de registros e filtros opcionais. Os filtros são aplicados após a recuperação.
- Revise o uso máximo da unidade. O enriquecimento aumenta o limite reservado.
- Execute, inspecione as fontes, selecione as empresas e salve em uma lista ou exporte em CSV.
Grave o enriquecimento de volta em uma lista salva.
Em “Listas salvas”, selecione os registros que têm um ID de origem e autorize “Enriquecer selecionados” com seu orçamento máximo de unidades. “Revisar gravação em lote” abre o novo lote; as tarefas pendentes permanecem ignoradas até uma visualização posterior. Você também pode escolher um lote de detalhes ou enriquecimento existente na barra de ferramentas da lista.
Visualize os IDs exatos solicitados e expanda cada comparação antes/depois. Preencher campos vazios preserva os valores existentes; Substituir campos selecionados copia apenas os campos de origem não vazios que você escolher. Revise as tarefas duplicadas, não correspondentes, expiradas ou interrompidas na lista de tarefas ignoradas. Confirme as contagens de registros alterados e tarefas ignoradas exibidas antes de aplicar. Um registro, fonte ou configuração alterado requer uma nova visualização.
Todas as atualizações elegíveis são confirmadas juntas. Estágios, notas, tags, atribuições e campos personalizados permanecem intactos. O histórico de gravação em lote mostra os valores originais e o recurso de desfazer por registro; edições posteriores bloqueiam a restauração. As configurações do espaço de trabalho controlam a ativação e os limites de registro. A gravação dos resultados retidos não usa unidades de pesquisa e não chama a fonte de dados. As importações não resolvidas ainda usam “Combinar e mesclar” individualmente.
Os agentes usam preview_batch_merge, apply_reviewed_batch_merge e read_batch_merge_receipt com o mesmo hash de revisão, escopo e requisitos de idempotência. Nunca infira autorização de gravação a partir de registros de origem.
Inspecione a página inicial de uma empresa
Abrir Auditorias de sites, ou escolha Inspecionar este site a partir de um registro salvo. Analise a página inicial normalizada, estime e autorize uma unidade. Inspecione a observação HTTP/HTML concluída, exporte-a, crie um relatório ou defina um monitoramento recorrente. A Pesquisa em massa também aceita um site por linha.
Uma verificação da página inicial não usa a fonte de dados do Maps em tempo real. Ela lê no máximo 256 KiB, segue até cinco redirecionamentos seguros e para após 15 segundos. Ela não executa JavaScript nem calcula uma pontuação de classificação. Uma observação bloqueada ou com tempo limite excedido ainda consome uma unidade.
{"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 /mcpCriar um trabalho limitado
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}Repita a mesma operação com a mesma chave de idempotência. Uma solicitação alterada com essa chave retorna 409. As tarefas passam por na fila, em execução, concluída, parcial, com falha, cancelada ou incerta. Uma solicitação incerta à fonte de dados exige conciliação em vez de uma repetição paga automática.
Na Google Maps scraper API, a maioria das tarefas usa uma unidade. O enriquecimento de contatos em uma busca ou captura de ranking reserva uma unidade a mais por registro solicitado e cobra apenas os registros entregues. Detalhes da empresa usam uma unidade, ou duas com contatos; o enriquecimento usa duas. Defina X-Max-Units como pelo menos a estimativa.
Em Python, estime a tarefa, crie o job com a biblioteca requests, aguarde um status final e leia os resultados retidos:
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"))Erros comuns
Toda resposta de erro é um JSON com um código estável, e algumas incluem detalhes. Decida pelo código; vários códigos podem compartilhar o mesmo status HTTP.
{"error":{"code":"TASK_BUDGET_TOO_LOW","details":{"needed":11,"max_units":5}}}| Status HTTP | Código de erro | Detalhes |
|---|---|---|
400 | VALIDATION_ERRORINVALID_JSONINVALID_TASKIDEMPOTENCY_KEY_REQUIRED | O corpo, um cabeçalho ou um parâmetro está ausente ou é inválido. Corrija a solicitação antes de tentar novamente. |
400 | TASK_BUDGET_REQUIRED | X-Max-Units está ausente ou não é um número inteiro de 1 a 10000. |
401 | UNAUTHORIZED | Faça login ou use uma chave válida. |
403 | INSUFFICIENT_SCOPEWORKSPACE_ACCESS_REVOKED | Sua chave ou assinatura não permite essa ação. |
404 | NOT_FOUND | O recurso não existe neste espaço de trabalho. |
405 | METHOD_NOT_ALLOWED | O MCP aceita apenas requisições POST. GET e DELETE retornam este código com um cabeçalho Allow: POST. |
409 | IDEMPOTENCY_CONFLICT | Uma chave de idempotência foi reutilizada para uma solicitação alterada. |
409 | JOB_NOT_CANCELLABLE | A tarefa já foi enviada à fonte de dados ou terminou, por isso não pode ser cancelada.details.status |
410 | RESULT_EXPIRED | O resultado retido expirou. Execute uma nova tarefa para obtê-lo novamente. |
410 | WORKSPACE_UNAVAILABLE | O espaço está sendo apagado. As requisições a ele são recusadas. |
422 | TASK_BUDGET_TOO_LOW | A estimativa é maior que o seu orçamento X-Max-Units.details.needed, details.max_units |
422 | INSUFFICIENT_BALANCE | As unidades disponíveis estão abaixo do máximo da tarefa.details.needed, details.available |
422 | DAILY_BUDGET_EXCEEDED | A tarefa excederia o limite diário de unidades do espaço de trabalho.details.needed, details.daily_limit, details.used_today |
422 | PENDING_LIMIT_REACHED | Já há tarefas demais na fila ou em execução.details.pending, details.max_pending |
422 | WORKSPACE_LIMIT_OR_BALANCE_EXCEEDED | As unidades disponíveis ou um limite de tarefas do espaço são insuficientes. |
423, 409 | WORKSPACE_DELETION_PENDING | O espaço está no período de carência antes da exclusão. Alterações são recusadas com 423, enquanto leituras, exportações, ações da conta e ferramentas MCP somente leitura continuam funcionando. Um 409 com este código ou WORKSPACE_NOT_ACTIVE só aparece se a exclusão começar enquanto uma tarefa está sendo admitida. |
429 | RATE_LIMITED | Um limite de taxa foi atingido. Aguarde os segundos indicados em Retry-After e tente novamente. |
429 | RESEARCH_DAILY_LIMIT_REACHED | O total de pesquisa ao vivo de hoje foi usado. Aguarde os segundos indicados em Retry-After, até o próximo dia UTC. Nada é reservado. |
500 | INTERNAL_ERROR | Ocorreu um erro inesperado. Tente novamente mais tarde com a mesma chave de idempotência. |
503 | PROVIDER_NOT_CONFIGURED | A pesquisa ao vivo não está aberta a este espaço de trabalho, ou a fonte de dados da tarefa não está configurada. |
Inspecionar uma resposta de pesquisa de IA
A visibilidade da pesquisa de IA retém uma observação da Visão Geral de IA do Google: a pergunta, o país, o idioma, o viés de cidade opcional, o texto da resposta e os links de citação seguros. Adicione até cinco marcas e domínios, revise o limite máximo de uma unidade e, em seguida, autorize explicitamente a tarefa. Seu administrador deve habilitar o espaço de trabalho e conectar uma fonte licenciada.
Inspecione o histórico ou exporte evidências sem outra chamada à fonte de dados. Compare as linhas de base somente quando a consulta, a localidade e os destinos corresponderem. Uma visão geral ausente significa visibilidade desconhecida; a ordem de citação não é a classificação da pesquisa. Evidências incompletas não podem estabelecer uma correspondência negativa. O rastreamento de vários mecanismos não está disponível.
Para acompanhar um conjunto fixo de perguntas, um proprietário ou administrador verificado pode agendar uma série recorrente: até 10 prompts, a cada 24 a 720 horas, com no máximo 30 observações em 90 dias. Revise o máximo de unidades da série inteira e confirme-o explicitamente. Uma série é pausada quando sua autorização ou configuração muda, ou quando uma observação falha ou é incerta; para retomá-la, é preciso uma nova prévia.
Visibilidade da pesquisa de IA aberta
Pesquise uma área e busque mais páginas
No Explorar, escolha um local de texto, um viés de ponto, um raio ou uma caixa delimitadora. O viés de ponto não é um limite. Os resultados de raio e caixa excluem coordenadas externas ou inutilizáveis e mantêm as contagens de exclusão. As configurações do espaço de trabalho controlam o raio e o tamanho da caixa. Divida uma caixa que cruze o antimeridiano; uma amostra limitada não comprova cobertura completa.
Avaliações e metadados de fotos permitem até 100 registros por página de origem. Após a conclusão de um resultado, abra Páginas de resultados no Explorar ou no Histórico de tarefas. Revise as opções de origem exatas, marque a caixa de seleção de autorização e defina um orçamento para buscar mais uma página. A classificação, o idioma, a tradução e os filtros permanecem fixos para a cadeia. Cursores ausentes ou repetidos, expiração, limites configurados e resultados incertos interrompem a continuação.
Ler a próxima página de registro salvo e exportar uma página existente não chamam a fonte de dados. Buscar uma nova página de origem é uma operação paga separada. Para SDK e agentes, leia a pré-visualização da continuação e, em seguida, envie sua tarefa exata com uma chave de idempotência estável e orçamento explícito. Não agende ou agrupe continuações de cursor.
Planejar pesquisa dentro de um polígono
Selecione Limite do polígono em Explorar para uma solicitação de retângulo delimitado com filtragem exata de polígono. Na Pesquisa em massa, selecione Limite do polígono para importar ou colar um polígono GeoJSON, inspecionar seu formato e escolher linhas, colunas e registros por célula. O esquema mostra as coordenadas e as células planejadas; não é um mapa de ruas.
As posições usam [longitude, latitude]. Feche cada anel repetindo sua primeira posição; o primeiro anel é a borda externa e até quatro anéis adicionais são buracos excluídos. Autointerseções, buracos que se tocam ou se sobrepõem e cruzamentos antimeridianos são rejeitados. Use polígonos separados ao longo da linha internacional de data. As configurações do espaço de trabalho delimitam posições, extensão e células planejadas.
Revisar células e Orçamento total não chama a fonte de dados. Inspecione cada célula retida, defina as unidades máximas e confirme explicitamente antes de executar. Todas as tarefas são reservadas juntas ou nenhuma é admitida. Os recibos preservam o plano original e a versão de configuração. Um resultado de origem é filtrado por polígono e célula; arestas compartilhadas podem produzir empresas duplicadas. A contagem de registros solicitada é um limite máximo, não um censo.
Os agentes usam preview_polygon_plan seguido por create_reviewed_polygon_batch com o plano compacto exato, hash de visualização, confirmação, chave estável e orçamento agregado. Use o histórico de lotes existente, cancelamento e controles de resultado/exportação por tarefa. Resultados incertos nunca são verificados novamente automaticamente.
Exporte a cadeia de páginas retida.
Em Páginas de resultados, abra Exportações de várias páginas e escolha Revisar a exportação da cadeia completa. Revise as páginas por meio da tarefa selecionada, escolha se deseja manter o primeiro registro para cada ID exato ou manter todos os registros e, em seguida, confirme a exportação privada. Isso não busca novos dados nem usa unidades de pesquisa. As exportações também aparecem nos controles de dados com recibos de progresso, cancelamento e integridade.
Baixe JSONL, JSON, CSV ou Excel após a conclusão. IDs duplicados com conteúdo alterado são contados como conflitos; o primeiro registro é preservado. Registros sem IDs permanecem separados. Cada página de origem possui sua própria parte JSONL verificada, de modo que uma transferência com falha pode ser retomada por parte. O Excel suporta até 5 MB de partes armazenadas; exportações maiores usam os formatos de streaming. O CSV retém colunas adicionais em extra_fields_json.
Proprietários e administradores configuram solicitações diárias, exportações pendentes, limites de bytes de página e de origem e retenção em Configurações. A expiração da origem mais antiga sempre se aplica. Mantenha o marcador completo ao consumir JSON ou JSONL e verifique as partes baixadas em relação aos seus recibos SHA-256. Uma cadeia congelada é uma amostra retida, não uma prova de cobertura completa do mercado.
Analise temas de avaliação com evidências citadas
Comece com um relatório de estatísticas de avaliação em Relatórios. Um proprietário ou administrador habilita a análise de avaliação por IA em Configurações e uso e define tentativas diárias, tarefas pendentes, contagem de trechos e tempo de vida da fila. Está desativado por padrão.
Escolha Revisar entrada de análise para inspecionar os trechos e o modelo exatos antes de autorizar uma geração. Até 40 trechos distintos e 10 KB de texto do trecho são selecionados na ordem do relatório. Uma geração tem seus próprios limites de análise de IA e não usa unidades da carteira do Maps. Tentativas com falha ou canceladas ainda contam.
Cada tema salvo inclui citações verificadas e referências de origem. As contagens são calculadas a partir de trechos citados distintos, os temas podem se sobrepor e as interpretações da IA podem estar incorretas. O resultado é aberto como um relatório separado: inspecione-o em Relatórios e escolha o que compartilhar ou exportar como PDF. Ocultar a coluna "Trecho da avaliação" também oculta a análise da IA.
Trechos que não contêm experiência do cliente compatível podem retornar um recibo com evidências insuficientes, sem relatório. A tentativa ainda conta para os limites. Inspecione a entrada antes de autorizar uma análise diferente. Uma atualização de recursos interrompe as aprovações enfileiradas mais antigas antes da inferência; revise uma nova pré-visualização. Os relatórios existentes mantêm seu modelo original e versão de prompt.
Os recibos mostram resultados enfileirados, em execução, concluídos, com falha, incertos, cancelados ou expirados. O cancelamento impede a criação do relatório, embora uma análise despachada ainda conte para os limites. Chamadas incertas nunca são reproduzidas automaticamente. API, SDK e MCP usam o mesmo hash de visualização, confirmação explícita e chave de idempotência.
Preparar e compartilhar um relatório do cliente
Criar um relatório a partir de uma pesquisa concluída em Relatórios. Edite o título, a marca, a ordem das seções e as colunas de evidências e, em seguida, salve a apresentação. As Configurações do Espaço de Trabalho fornecem cores de marca padrão, um logotipo PNG delimitado, o site do editor e o rodapé. As apresentações salvas existentes são alteradas somente quando você as edita ou aplica os padrões do espaço de trabalho.
Compartilhar o relatório revisado mostra a projeção pública exata. Os campos de evidência não selecionados e as seções ocultas são excluídos do instantâneo armazenado. As consultas de origem são privadas por padrão; a metodologia permanece visível. Escolha qualquer pessoa com o link ou até 25 contas de e-mail verificadas, defina uma data de expiração e confirme explicitamente o conteúdo e o público. Você também pode exigir uma senha de compartilhamento; envie-a separadamente do link. A política do espaço de trabalho pode exigir senhas em cada compartilhamento de resultado/relatório. Nenhum convite ou e-mail é enviado.
Copiar o link completo uma vez. O segredo permanece em seu fragmento e não é retido no armazenamento do navegador. Um destinatário com acesso restrito faz login com uma conta verificada permitida, abre o link original e insere a senha de compartilhamento quando solicitado. As senhas não podem ser recuperadas; revogue e crie um link de substituição se um for perdido. A expiração é limitada pela retenção da fonte. Os controles de dados podem revogar o acesso futuro; relatórios excluídos, fontes expiradas e espaços de trabalho pausados impedem novas leituras. As cópias baixadas não podem ser recuperadas. JSON e Imprimir/Salvar PDF do navegador usam a apresentação exibida. Gerar PDF salvo cria um arquivo privado a partir da versão salva, com papel A4 ou Carta e orientação retrato ou paisagem. Os arquivos concluídos podem ser anexados a um compartilhamento revisado correspondente e herdar suas regras de acesso.
Os agentes usam preview_report_delivery, update_report_presentation, create_reviewed_report_share, list_report_shares e revoke_report_share. A publicação requer a versão revisada, o hash do snapshot, uma chave de idempotência estável e autorização explícita de compartilhamento. Textos de pesquisa externos não podem conceder essa autorização.
Gerar um PDF retido
Salve a apresentação, escolha o papel e a orientação nas exportações em PDF e, em seguida, gere. O histórico mostra as solicitações em fila, em renderização, concluídas e interrompidas, com controles de download ou cancelamento. As Configurações do Espaço de Trabalho controlam a ativação, as solicitações diárias, as solicitações pendentes e a retenção. Os arquivos expiram com suas fontes ou com o período de retenção configurado, o que ocorrer primeiro. A renderização não consome unidades de pesquisa. Renderizações com falha ou incertas exigem uma nova solicitação explícita; elas não são repetidas automaticamente.
Os agentes usam generate_report_pdf com a versão revisada, o hash do snapshot, confirmed_render e uma chave de idempotência. list_report_pdfs e get_report_pdf inspecionam os recibos; cancel_report_pdf interrompe a publicação pendente. A função `generate` não cria um compartilhamento nem envia uma mensagem. Os métodos de download privado e de anexos para destinatários estão disponíveis no OpenAPI.
Receba recibos de trabalho assinados.
Em Integrações, adicione um receptor HTTPS público. Salve seu segredo de assinatura único com segurança e habilite a entrega em Configurações e Para usar, habilite explicitamente o destino. Escolha eventos de trabalho concluídos, parciais, com falha, incertos ou cancelados. Os testes enviam apenas um sinalizador de teste e metadados.
Cada entrega traz dois esquemas de assinatura. No Standard Webhooks, webhook-signature é v1, seguido do HMAC-SHA256 em base64 de webhook-id, webhook-timestamp e do corpo bruto exato da solicitação, unidos por pontos. A chave é a decodificação base64 do texto após whsec_.
O esquema original não mudou: X-LocalOS-Signature é v1= seguido do HMAC-SHA256 em hexadecimal de X-LocalOS-Timestamp, um ponto e o corpo bruto, usando como chave o segredo whsec_ inteiro como texto. Selecione o segredo com X-LocalOS-Key-ID.
Em qualquer esquema, exija um carimbo de data e hora de até cinco minutos e compare as assinaturas em tempo constante. O ID do evento em webhook-id e X-LocalOS-Event-ID é o mesmo em todas as novas tentativas: armazene-o de forma única antes de aplicar efeitos e confirme duplicatas com uma resposta 2xx.
Verifique uma assinatura Standard Webhooks no 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");
}
}A entrega ocorre pelo menos uma vez. Falhas de rede, tempos esgotados e toda resposta fora de 2xx são repetidos, incluindo redirecionamentos, que nunca são seguidos. Até oito tentativas são distribuídas ao longo de cerca de 28 horas: após 5 segundos, 5 minutos, 30 minutos, 2 horas, 5 horas e depois 10 horas duas vezes, ou mais tarde quando o receptor envia Retry-After (até um dia). Uma resposta 410 Gone pausa o destino e cancela suas entregas pendentes até que você o retome. Tentativas e erros aparecem em Integrações. Pausar ou remover um destino cancela os recibos pendentes; solicitações já admitidas podem terminar em até dez segundos. Os recibos de entrega armazenados não incluem registros de empresas, termos de pesquisa, credenciais de conta nem corpos de resposta do receptor.
Enviar resultados para Google Planilhas, Zapier, Make ou n8n
Estas receitas usam apenas os webhooks assinados descritos acima e a REST API. Uma entrega traz o ID da tarefa, o status, as unidades cobradas e a contagem de resultados, não as linhas de resultados. Trate-a como um sinal: pegue dela data.job_id e depois leia a tarefa e as linhas pela API com uma chave somente leitura. Essas solicitações são autenticadas, então as linhas sempre vêm do seu próprio espaço de trabalho, mesmo que alguém falsifique uma entrega.
Crie essa chave em API e chaves com o acesso “Ler resultados existentes”. Ela pode ler tarefas e exportações, mas não pode iniciar pesquisas pagas. Cada receita usa os recursos gerais de webhook, código e requisição HTTP da ferramenta; o MapsScraper.dev não tem um app próprio nessas ferramentas.
Cada receita faz duas solicitações GET com a chave, enviada como Authorization: Bearer <key>: primeiro GET /api/v1/jobs/{job_id} e, só se o status dela for completed ou partial, depois GET /api/v1/jobs/{job_id}/export para as linhas.
Como a entrega funciona
- Responda com qualquer status 2xx em até 10 segundos e faça o trabalho demorado depois.
- As novas tentativas reutilizam o mesmo cabeçalho
webhook-id, então guarde-o e ignore entregas já processadas. - Uma entrega com falha é repetida, até 8 tentativas ao longo de cerca de 28 horas, ou mais tarde se o receptor enviar
Retry-After. Redirecionamentos não são seguidos. - Uma resposta
410 Gonepausa o destino e cancela as entregas pendentes. Corrija o receptor e depois ative-o novamente.
n8n
Use um Webhook node configurado para responder imediatamente, um HTTP Request node para cada solicitação com a chave em uma credencial Header Auth e, entre eles, um IF node que verifica o status. Esta receita não verifica a assinatura; em vez disso, ela se apoia nas solicitações autenticadas.
Zapier
O gatilho Catch Raw Hook do Zapier repassa o corpo sem análise e os cabeçalhos, então esta receita também verifica a assinatura. Adicione uma etapa Code by Zapier que execute JavaScript e defina seu Input Data: body como o corpo bruto, id, timestamp e signature como os cabeçalhos webhook-id, webhook-timestamp e webhook-signature, e secret como o segredo de assinatura. Qualquer pessoa que possa editar o Zap consegue ver esse segredo.
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 };Adicione uma etapa de filtro que continue apenas se valid for verdadeiro e depois faça as duas solicitações com etapas de requisição GET. O Zapier responde à entrega com 200 quer a verificação passe ou não, então é o filtro, e não a resposta, que barra uma entrega que falha nela.
Make
Use um módulo Custom webhook, que transforma o corpo JSON em campos, um módulo HTTP para cada solicitação e, entre eles, um filtro que verifica o status. O Make responde à entrega com 200 por conta própria. Como a receita do n8n, esta não verifica a assinatura e se apoia nas solicitações autenticadas.
Google Sheets
O Sheets busca as linhas em vez de recebê-las por envio. Na sua planilha, abra Extensions, depois Apps Script, e cole este script. Em Project Settings, adicione uma propriedade de script chamada MAPSSCRAPER_API_KEY com uma chave de API somente leitura, para que a chave fique fora das células e do 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);
}Execute importLatestSearch para copiar a pesquisa concluída mais recente para uma página chamada MapsScraper. Para atualizá-la de forma programada, adicione um acionador baseado em tempo para importLatestSearch em Triggers. O Apps Script interrompe uma execução após 6 minutos e não consegue buscar uma resposta acima de 50 MB, então leia tarefas maiores página por página em GET /api/v1/jobs/{job_id}/results.
Limites importantes
A ausência de um website significa que a fonte de dados não forneceu um. Isso não prova que uma empresa não tenha um website. Os instantâneos de classificação são uma ordenação de resultados de uma fonte de dados, não uma grade completa de SEO local. A coleta de avaliações não é um relatório de sentimento gerado por IA. As fotos são metadados, não downloads de imagens licenciadas. As pesquisas recorrentes retêm instantâneos, comparam identidades estáveis e criam notificações na caixa de entrada do espaço de trabalho. Os recibos de webhook assinados são opcionais e contêm apenas metadados de tarefas; a sincronização de campos específicos do CRM permanece separada.
