Integrações
Webhooks
Mandamos um POST para o seu servidor quando uma coleta termina, e o seu código não precisa ficar perguntando se já acabou.
Quais planos têm webhooks
Os webhooks vêm nos planos Starter, Pro e Scale. No plano Free, toda chamada de webhook responde 402 PLAN_GATE e não mandamos nada. O details.upgradeTo diz para qual plano mudar: starter. Mude de plano no console.
Sem webhooks, o seu código pode ler a coleta com GET /v1/collections/{id} até ela terminar, ou acompanhar o progresso ao vivo dela.
Adicione um endpoint
Um endpoint é o endereço no seu servidor para onde mandamos o POST. Adicione um com esta chamada ou na página Webhooks do console:
curl -X POST https://api.gmaps.dev/v1/webhooks \
-H "Authorization: Bearer $GMAPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/webhooks/gmaps" }'A resposta traz o secret, o segredo de assinatura, que começa com whsec_. É a única vez que ele aparece. Guarde-o com as suas outras credenciais: é com ele que o seu servidor confere se a requisição veio da gente.
Há mais dois campos, ambos opcionais. O events escolhe quais eventos mandar; sem ele, vão os dois. O description é uma anotação para você, como qual dos seus serviços é este.
O que a URL precisa ter
Conferimos a URL quando você adiciona e de novo antes de cada envio, porque o endereço por trás de um nome pode mudar:
- Começa com
https://. Nunca mandamos os seus resultados por uma conexão sem criptografia. - O nome aponta para um endereço público. Redes privadas e a sua própria máquina, como
localhost, são recusadas. Para testar no seu notebook, use uma ferramenta que dê a ele um endereço https público. - Não tem usuário nem senha. Quem prova que a requisição veio da gente é a assinatura.
- Ainda não é um dos seus endpoints. Adicionar a mesma URL duas vezes responde
409 CONFLICT. - É o endereço final. Não seguimos redirecionamentos.
Eventos
| Evento | Quando mandamos |
|---|---|
collection.completed | Uma coleta terminou com status succeeded ou partial. Coletas explica os dois. As contagens vêm zeradas quando a busca não encontrou nada. |
collection.failed | Uma coleta terminou sem nenhum lugar, porque as buscas não conseguiram rodar. Ela não custou nada. O data.error diz por quê e o que fazer. |
ping | Só quando você manda um teste. |
Uma coleta que você cancela não manda nada: você já sabe que ela parou.
O que mandamos
Um JSON pequeno, com contagens e um link, nunca os lugares em si. Um collection.completed fica assim:
{
"id": "3f6c2a9e-1b7d-4e58-8a21-6d0f4b9c2e75",
"timestamp": "2026-09-24T14:05:12.345Z",
"type": "collection.completed",
"data": {
"collection": {
"id": "8c1f5e2a-7b3d-4f60-9e14-2a6d0b9c7f31",
"status": "succeeded",
"stopReason": null
},
"counts": {
"placesDelivered": 212,
"placesFresh": 180,
"placesCached": 32,
"creditsCharged": 180,
"creditsRefunded": 0
},
"resultUrl": "https://api.gmaps.dev/v1/collections/8c1f5e2a-7b3d-4f60-9e14-2a6d0b9c7f31/places"
}
}id: o id do próprio evento. Ele é o mesmo em todas as tentativas.typeetimestamp: qual é o evento e quando aconteceu.data.collection: oide ostatusda coleta, e ostopReason, que diz por que ela parou antes ou não encontrou nada. Ele vemnullquando a coleta buscou a região inteira ou chegou aomaxPlaces. Coletas explica cada motivo.data.counts: os lugares que a coleta entregou e os créditos que cobrou, como na própria coleta. Coletas explica cada contagem.data.resultUrl: onde ler os lugares, uma página por vez, com a sua chave de API.- Um
collection.failedtrazdata.errorno lugar das contagens e do link, com os mesmoscode,messageemessageKeyde qualquer erro da API.
Toda requisição também leva estes cabeçalhos:
| Cabeçalho | O que traz |
|---|---|
content-type | application/json |
x-gmaps-signature | Quando mandamos e a assinatura. Veja abaixo. |
x-gmaps-delivery | O id do evento de novo, para você reconhecer um repetido antes de ler o corpo. |
user-agent | gmaps.dev webhooks |
Valide a assinatura
Qualquer pessoa que descobrir a sua URL pode mandar uma requisição para ela. A assinatura prova que a requisição veio da gente. Ela vem no cabeçalho x-gmaps-signature:
x-gmaps-signature: t=1790000000,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08té quando mandamos, em segundos Unix.v1é um HMAC-SHA256 det, um ponto e o corpo bruto. Ele é feito com o segredo de assinatura inteiro, incluindo owhsec_, e escrito em hexadecimal. Um HMAC é um código que só quem tem o segredo consegue gerar.
Para validar, gere o mesmo HMAC e compare os dois. Recuse a requisição se forem diferentes ou se t estiver a mais de 5 minutos do seu relógio, para ninguém conseguir reenviar uma requisição antiga.
Use o corpo bruto, exatamente os bytes que chegaram. Se você fizer o parse do JSON e serializar de novo, os bytes mudam e a validação falha.
O verifyWebhook do SDK faz tudo isso:
import { verifyWebhook } from "gmaps-sdk";
const body = await request.text();
const signed = await verifyWebhook(
process.env.GMAPS_WEBHOOK_SECRET,
body,
request.headers.get("x-gmaps-signature"),
);
if (!signed) return new Response(null, { status: 400 });
const event = JSON.parse(body);Sem o SDK, são poucas linhas em qualquer linguagem. Em Python, fica assim:
import hashlib, hmac, time
def verify(secret: str, body: bytes, header: str | None) -> bool:
if not header:
return False
parts = dict(part.strip().split("=", 1) for part in header.split(",") if "=" in part)
try:
sent = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - sent) > 300:
return False
signed = f"{sent}.".encode() + body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Respostas e novas tentativas
Responda com qualquer status 2xx assim que a requisição chegar e deixe o trabalho demorado para depois. Qualquer outra resposta conta como falha:
- Se recebermos um 5xx,
408,425ou429, ou se a conexão falhar ou ficar sem resposta, tentamos de novo, até 5 tentativas no total. A espera prevista começa em 30 segundos e dobra após cada tentativa que falha. Se o cabeçalhoRetry-Afterpedir mais tempo, usamos esse prazo naquela tentativa, limitado a uma hora. - Qualquer outro 4xx: não tentamos de novo. O seu servidor disse não, e diria não outra vez.
- Um redirecionamento: não seguimos e não tentamos de novo. O mesmo vale para um domínio que não aponta para nenhum endereço ou que aponta para uma rede privada. Aponte o endpoint para o endereço final e público do seu servidor.
Por causa das novas tentativas, o mesmo evento pode chegar mais de uma vez. Guarde os ids que você já tratou e ignore os repetidos.
Quando paramos de enviar
Se 10 entregas seguidas falharem, desligamos o endpoint. Cada entrega conta uma vez, quando não há mais tentativas. Dá para ver isso em dois lugares:
- A página Webhooks do console mostra o endpoint como desligado e diz o motivo.
- O
GET /v1/webhooksresponde comenabled: false, e odisabledReasondiz por quê, em inglês, com o último erro. OdisabledReasonKeytraz o mesmo motivo como chave de mensagem, e odisabledReasonParams, os valores que completam a frase. Assim, um programa mostra o motivo no idioma de quem lê.
Enquanto ele está desligado, os eventos que ele perde não ficam guardados, então nunca chegam. Para se atualizar, liste as suas coletas com GET /v1/collections.
Corrija o seu servidor e ligue o endpoint de novo. A contagem de falhas recomeça do zero:
curl -X POST https://api.gmaps.dev/v1/webhooks/4d7a1c3e-9b2f-4e8a-b6d1-0c5f7e2a9b34 \
-H "Authorization: Bearer $GMAPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'A contagem também volta a zero sempre que uma entrega dá certo.
Mande um teste
Antes que uma coleta de verdade dependa disso, mande um evento ping. Ele mostra se o seu código lê a requisição e se a assinatura confere:
curl -X POST https://api.gmaps.dev/v1/webhooks/4d7a1c3e-9b2f-4e8a-b6d1-0c5f7e2a9b34/test \
-H "Authorization: Bearer $GMAPS_API_KEY"- A resposta vem na hora, com a entrega quase sempre ainda
pending. O teste é assinado e tentado de novo como um evento de verdade. - Mande
messageno corpo para colocar um texto seu no evento e achá-lo fácil nos seus logs. - Não dá para testar um endpoint desligado: a chamada responde
409 CONFLICT. Ligue antes.
Veja o que mandamos
O histórico de entregas lista o que mandamos para um endpoint, das mais recentes para as mais antigas. Olhe aqui quando um evento não chegar:
curl https://api.gmaps.dev/v1/webhooks/4d7a1c3e-9b2f-4e8a-b6d1-0c5f7e2a9b34/deliveries \
-H "Authorization: Bearer $GMAPS_API_KEY"status:pendingenquanto ainda estamos tentando, depoisdeliveredoufailed.attempts: quantas tentativas já foram feitas.responseStatus: o status que o seu servidor respondeu, ounullse ele nunca respondeu.error: o que deu errado e como resolver, em inglês. OerrorKeytraz a mesma frase como chave de mensagem, e oerrorParams, os valores que a completam. Assim, um programa mostra a frase no idioma de quem lê.
Guardamos o histórico por 7 dias. O limit define quantas entregas voltam.
Altere ou exclua um endpoint
O POST /v1/webhooks/{id} muda events, description ou enabled. Mande só o que quer mudar: o resto fica como estava.
- Para pausar um endpoint, coloque
enabledemfalse. Ele mantém o segredo de assinatura, então o seu código não muda quando você ligar de novo. - O
DELETE /v1/webhooks/{id}remove o endpoint e o histórico de entregas dele. Não dá para desfazer. - Não dá para mudar a URL nem o segredo de assinatura. Para trocar, exclua o endpoint e adicione de novo.