Integraciones
Webhooks
Enviamos un POST a tu servidor cuando termina una recopilación, para que tu código no tenga que estar preguntando si ya acabó.
Qué planes los incluyen
Los webhooks vienen en los planes Starter, Pro y Scale. En el plan Free, toda llamada de webhooks responde 402 PLAN_GATE y no enviamos nada. details.upgradeTo indica a qué plan cambiar: starter. Cambia de plan en la consola.
Sin webhooks, tu código puede leer la recopilación con GET /v1/collections/{id} hasta que termine, o seguir su progreso en vivo.
Agrega un endpoint
Un endpoint es la dirección de tu servidor a la que enviamos el POST. Agrega uno con esta llamada, o en la página Webhooks de la consola:
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" }'La respuesta incluye secret, el secreto de firma, que empieza con whsec_. Es la única vez que lo mostramos. Guárdalo junto con tus otros secretos: con él, tu servidor comprueba que una solicitud vino de nosotros.
Hay dos campos más, ambos opcionales. events elige qué eventos enviar; si lo omites, se envían los dos. description es una nota para ti, por ejemplo cuál de tus servicios es este.
Qué necesita la URL
Revisamos la URL cuando la agregas, y otra vez antes de cada envío, porque la dirección detrás de un nombre puede cambiar:
- Empieza con
https://. Nunca enviamos tus resultados por una conexión sin cifrar. - Su nombre apunta a una dirección pública. Las redes privadas y tu propia máquina, como
localhost, se rechazan. Para probar desde tu computadora, usa una herramienta que le dé una dirección https pública. - No lleva usuario ni contraseña. Lo que prueba que una solicitud vino de nosotros es la firma.
- Todavía no es uno de tus endpoints. Si agregas la misma URL dos veces, la respuesta es
409 CONFLICT. - Es la dirección final. No seguimos redirecciones.
Eventos
| Evento | Cuándo lo enviamos |
|---|---|
collection.completed | Una recopilación terminó con estado succeeded o partial. Recopilaciones explica los dos. Sus conteos son cero cuando la búsqueda no encontró nada. |
collection.failed | Una recopilación terminó sin ningún lugar, porque sus búsquedas no pudieron ejecutarse. No costó nada. data.error dice por qué y qué hacer. |
ping | Solo cuando envías una prueba. |
Una recopilación que cancelas no envía nada: ya sabes que se detuvo.
Qué enviamos
Un cuerpo JSON pequeño, con conteos y un enlace, nunca los lugares en sí. Un collection.completed se ve así:
{
"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: el id del propio evento. Es el mismo en cada reintento.typeytimestamp: qué evento es y cuándo ocurrió.data.collection: elidy elstatusde la recopilación, ystopReason, que dice por qué se detuvo antes o no encontró nada. Esnullcuando la recopilación buscó toda la zona o llegó a sumaxPlaces. Recopilaciones explica cada motivo.data.counts: los lugares que entregó la recopilación y los créditos que cobró, igual que en la propia recopilación. Recopilaciones explica cada conteo.data.resultUrl: dónde leer los lugares, una página a la vez, con tu clave de API.- Un
collection.failedtraedata.erroren lugar de los conteos y el enlace, con los mismoscode,messageymessageKeyque cualquier error de la API.
Cada solicitud también lleva estos encabezados:
| Encabezado | Qué contiene |
|---|---|
content-type | application/json |
x-gmaps-signature | Cuándo la enviamos, y la firma. Mira más abajo. |
x-gmaps-delivery | El id del evento otra vez, para que detectes uno repetido antes de leer el cuerpo. |
user-agent | gmaps.dev webhooks |
Verifica la firma
Cualquiera que encuentre tu URL puede enviarle una solicitud. La firma prueba que una solicitud vino de nosotros. Va en el encabezado x-gmaps-signature:
x-gmaps-signature: t=1790000000,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08tes cuándo la enviamos, en segundos Unix.v1es un HMAC-SHA256 det, un punto y el cuerpo sin procesar. Se calcula con tu secreto de firma completo, incluidowhsec_, y se escribe en hexadecimal. Un HMAC es un código que solo puede generar quien tiene el secreto.
Para verificarla, calcula el mismo HMAC y compara los dos. Rechaza la solicitud si son distintos, o si t está a más de 5 minutos de tu reloj, para que nadie pueda reenviar una solicitud antigua.
Usa el cuerpo sin procesar: los bytes exactos que recibiste. Si analizas el JSON y lo vuelves a convertir en texto, los bytes cambian y la verificación falla.
verifyWebhook del SDK hace todo esto:
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);Sin el SDK, son unas pocas líneas en cualquier lenguaje. En Python se ve así:
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", ""))Respuestas y reintentos
Responde con cualquier estado 2xx en cuanto llegue la solicitud, y deja el trabajo lento para después. Cualquier otra respuesta cuenta como una falla:
- Un 5xx,
408,425,429, un tiempo de espera agotado o una conexión fallida: lo intentamos de nuevo, hasta 5 intentos en total. La espera habitual empieza en 30 segundos y se duplica después de cada intento fallido. Si el encabezadoRetry-Afterpide más tiempo, usamos ese plazo para ese intento, con un tope de una hora. - Cualquier otro 4xx: no lo intentamos de nuevo. Tu servidor dijo que no, y volvería a decir que no.
- Una redirección: no la seguimos y no lo intentamos de nuevo. Lo mismo pasa con un dominio que no apunta a ninguna dirección, o que apunta a una red privada. Apunta el endpoint a la dirección final y pública de tu servidor.
Por los reintentos, el mismo evento puede llegar más de una vez. Guarda los id que ya procesaste y omite los repetidos.
Cuándo dejamos de enviar
Si fallan 10 entregas seguidas, desactivamos el endpoint. Cada entrega cuenta una vez, cuando dejamos de reintentarla. Lo puedes ver en dos lugares:
- La página Webhooks de la consola muestra el endpoint como desactivado y dice por qué.
GET /v1/webhooksresponde conenabled: false, ydisabledReasondice por qué, en inglés, con el último error.disabledReasonKeyes el mismo motivo como clave de mensaje, ydisabledReasonParamstrae los valores que completan la frase. Así, un programa puede mostrar el motivo en el idioma de quien lo lee.
Mientras está desactivado, los eventos que se pierde no se guardan, así que nunca llegan. Para ponerte al día, lista tus recopilaciones con GET /v1/collections.
Arregla tu servidor y vuelve a activar el endpoint. Su conteo de fallas empieza de nuevo desde cero:
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 }'El conteo también vuelve a cero cada vez que una entrega funciona.
Envía una prueba
Antes de que una recopilación real dependa de esto, envía un evento ping. Muestra si tu código lee la solicitud y si la firma coincide:
curl -X POST https://api.gmaps.dev/v1/webhooks/4d7a1c3e-9b2f-4e8a-b6d1-0c5f7e2a9b34/test \
-H "Authorization: Bearer $GMAPS_API_KEY"- Responde al instante con la entrega, casi siempre todavía
pending. La prueba se firma y se reintenta igual que un evento real. - Envía
messageen el cuerpo para poner tu propio texto en el evento y encontrarlo fácil en tus logs. - Un endpoint desactivado no se puede probar: la llamada responde
409 CONFLICT. Actívalo primero.
Mira qué enviamos
El historial de entregas lista lo que enviamos a un endpoint, de lo más reciente a lo más antiguo. Míralo cuando un evento no llegue:
curl https://api.gmaps.dev/v1/webhooks/4d7a1c3e-9b2f-4e8a-b6d1-0c5f7e2a9b34/deliveries \
-H "Authorization: Bearer $GMAPS_API_KEY"status:pendingmientras seguimos intentando, luegodeliveredofailed.attempts: cuántos intentos lleva.responseStatus: el estado con que respondió tu servidor, onullsi nunca respondió.error: qué salió mal y cómo arreglarlo, en inglés.errorKeyes la misma frase como clave de mensaje, yerrorParamstrae los valores que la completan. Así, un programa puede mostrarla en el idioma de quien lee.
Guardamos el historial 7 días. limit define cuántas entregas devuelve.
Cambia o elimina un endpoint
POST /v1/webhooks/{id} cambia events, description o enabled. Envía solo lo que quieras cambiar: lo demás queda como estaba.
- Para pausar un endpoint, pon
enabledenfalse. Conserva su secreto, así que tu código no cambia cuando lo vuelvas a activar. DELETE /v1/webhooks/{id}elimina el endpoint y su historial de entregas. No se puede deshacer.- La URL y el secreto no se pueden cambiar. Para tener otros, elimina el endpoint y agrégalo de nuevo.