Pular para o conteúdo

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

EventoQuando mandamos
collection.completedUma coleta terminou com status succeeded ou partial. Coletas explica os dois. As contagens vêm zeradas quando a busca não encontrou nada.
collection.failedUma 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.
pingSó 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.
  • type e timestamp: qual é o evento e quando aconteceu.
  • data.collection: o id e o status da coleta, e o stopReason, que diz por que ela parou antes ou não encontrou nada. Ele vem null quando a coleta buscou a região inteira ou chegou ao maxPlaces. 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.failed traz data.error no lugar das contagens e do link, com os mesmos code, message e messageKey de qualquer erro da API.

Toda requisição também leva estes cabeçalhos:

CabeçalhoO que traz
content-typeapplication/json
x-gmaps-signatureQuando mandamos e a assinatura. Veja abaixo.
x-gmaps-deliveryO id do evento de novo, para você reconhecer um repetido antes de ler o corpo.
user-agentgmaps.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=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
  • t é quando mandamos, em segundos Unix.
  • v1 é um HMAC-SHA256 de t, um ponto e o corpo bruto. Ele é feito com o segredo de assinatura inteiro, incluindo o whsec_, 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, 425 ou 429, 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çalho Retry-After pedir 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/webhooks responde com enabled: false, e o disabledReason diz por quê, em inglês, com o último erro. O disabledReasonKey traz o mesmo motivo como chave de mensagem, e o disabledReasonParams, 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 message no 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: pending enquanto ainda estamos tentando, depois delivered ou failed.
  • attempts: quantas tentativas já foram feitas.
  • responseStatus: o status que o seu servidor respondeu, ou null se ele nunca respondeu.
  • error: o que deu errado e como resolver, em inglês. O errorKey traz a mesma frase como chave de mensagem, e o errorParams, 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 enabled em false. 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.