Comportamiento de las entregas

El contenido estándar de la solicitud, el límite de 5 segundos, por qué no hay reintentos y qué significa cada estado de entrega.

La entrega de webhooks es sencilla por diseño, y esa sencillez tiene consecuencias para tu receptor: un solo intento HTTP, cinco segundos, ningún reintento. Lee esta página antes de decidir cuánto trabajo ejecutará tu manejador de forma síncrona.

La estructura estándar de los datos

Todos los eventos excepto listing.submission llegan con la misma estructura de nivel superior, con los campos específicos del evento anidados bajo data.

JSON
{  "event": "connection.location_connected",  "timestamp": "2026-08-25T14:32:10Z",  "account_id": 11073,  "location_id": "279381",  "data": {    "platform": "google",    "connected_account_id": "ba03bc4b-9f8c-4d3b-8e93-9628034c63cc"  }}
CampoTipoNotas
eventstringEl nombre del evento, con sus partes separadas por puntos. Úsalo para decidir qué lógica ejecutar.
timestampstringFecha y hora en UTC con formato ISO-8601, registradas cuando se preparó la entrega, no cuando ocurrió el cambio subyacente.
account_idintegerLa cuenta a la que pertenece el evento.
location_idstringUna cadena numérica: "279381", no 279381. No se convierte en interaction.*; consulta la excepción más abajo.
dataobjectCampos específicos del evento. Se documentan para cada evento en la referencia de eventos.
agency_account_idintegerSolo está presente cuando la cuenta está gestionada por una cuenta de agencia superior.

Los campos opcionales dentro de data se omiten cuando están vacíos, en lugar de enviarse como null. Programa de forma defensiva: trata una clave ausente y un null explícito de la misma manera, y nunca des por hecho que una clave documentada como opcional estará presente.

Dos excepciones

El plazo de 5 segundos

Cada entrega consiste en un único POST con un tiempo de espera de conexión de 5 segundos y un tiempo de espera de lectura de 5 segundos. Si tu endpoint no ha respondido a tiempo, el intento se abandona y se registra como timeout.

Ese plazo abarca toda la ejecución de tu manejador. El único diseño seguro es el siguiente:

  1. Verifica la firma.
  2. Guarda los datos en un medio de almacenamiento persistente: una cola, una tabla o un registro.
  3. Devuelve 200.

Todo lo demás (enriquecer los datos, distribuirlos entre varios destinos, volver a llamar a la API REST o cualquier operación que interactúe con un tercero) debe ejecutarse al procesar los datos de esa cola.

No hay reintentos

Cada entrega se intenta exactamente una vez. No hay esperas progresivas, cola de reintentos ni cola de mensajes no entregados. Si tu endpoint no está disponible durante dos minutos, los eventos que se generaron en esos dos minutos se pierden. No llegarán más tarde.

Conviene dejar claras dos consecuencias:

  • No se siguen las redirecciones. Las redirecciones están completamente deshabilitadas, por lo que un 301 o un 302 de tu endpoint se registra como una entrega fallida, sin seguir la redirección a una segunda URL. Configura la URL de tus webhooks con la URL final. Presta atención a las redirecciones del dominio sin subdominio a www y a las que añaden una barra final, que muchos servidores web incluyen de forma predeterminada.
  • Solo un 2xx cuenta como entrega completada. 3xx, 4xx y 5xx se registran como fallos.

Duplicados y orden

  • Puedes recibir el mismo evento dos veces y el mismo id para dos cambios distintos. Los identificadores dentro de data (un id de interacción, un id de elemento de publicación, un id de cuenta conectada) identifican el recurso, no el cambio. interaction.review vuelve a emitirse con el mismo id de interacción cada vez que cambia la reseña, y ningún cuerpo de evento incluye una versión ni un campo updated-at, por lo que un manejador que descarta de forma permanente los eventos según el id + event descarta cambios reales sin avisar. En su lugar, detecta duplicados exactos: calcula un hash de los bytes sin procesar del cuerpo e ignora las repeticiones del mismo hash dentro de un intervalo breve y limitado (minutos, no indefinidamente). Un cuerpo distinto para el mismo id es un cambio nuevo, no un duplicado. Después, vuelve a consultar el recurso mediante la API REST y considera esa lectura como la fuente de referencia; usa el id del recurso como clave para la escritura en lugar de omitirla.
  • El orden no está garantizado, y timestamp no lo corregirá. Los eventos proceden de varios canales independientes y se entregan a medida que llegan, por lo que un local_post.published puede llegar antes del local_post.created que esperabas recibir primero. No ordenes dos cambios del mismo recurso por timestamp: se registra cuando se prepara la entrega, no cuando se produjo el cambio, y no hay ninguna versión ni número de secuencia en el cuerpo del evento. Cuando el orden sea relevante, vuelve a consultar el recurso y usa lo que devuelva la API.
  • profile.updated se limita a una entrega por ubicación cada 60 segundos, que se realiza al inicio del intervalo. Los eventos se suprimen, no se retrasan: la primera modificación abre el intervalo y se entrega, todas las modificaciones dentro del intervalo se descartan y nunca llegan, y changed_fields describe únicamente la modificación que abrió el intervalo. Por tanto, el evento te llega al inicio del intervalo, de modo que una nueva lectura activada por su llegada solo puede mostrar esa primera modificación. Programa una segunda lectura una vez cerrado el intervalo, al menos 60 segundos después del evento, y reconcilia también las ubicaciones de forma periódica, porque la propia notificación puede perderse: un solo intento, sin reintentos. Esta limitación no se aplica a ningún otro evento.

Recuperación ante un evento perdido

No hay reintentos ni reproducción de eventos, por lo que cada familia necesita una lectura de referencia a la que puedas recurrir. Reconcilia los datos de forma periódica, no solo cuando algo parezca incorrecto.

FamiliaVolver a consultar conIntervalo sugeridoLo que una lectura no puede recuperar
listing.submissionGET /locations/{locationId}/listings/premium, una vez con listingType=premium y otra con additional: los valores de syncStatus y listingUrl por sitio que anuncia el evento.A diario por ubicación y después de una modificación masiva de ubicaciones.El cuerpo del evento heredado no contiene timestamp ni account_id, por lo que un evento perdido no puede situarse en el tiempo ni atribuirse a una cuenta. Ninguna lectura expone error_message de un envío de incomplete.
profile.*GET /locations, GET /locations/search o GET /locations-by-ids. El valor de updatedAt de cada ubicación te indica cuáles han cambiado.Cada hora, además de la lectura posterior al cierre del intervalo descrita anteriormente.source (user / api / bulk_edit) y changed_fields: una lectura muestra el estado actual, nunca quién cambió qué. Las modificaciones suprimidas durante el intervalo de 60 segundos no dejan ningún rastro, y el estado de una ubicación anterior a su eliminación se pierde.
connection.*GET /connected-accounts y GET /connected-accounts/{connectedAccountId}/details para la conexión, la desconexión y el estado de la autenticación. Para el par de eventos de verificación de Google, consulta googleVerificationStatus { status, message } en cualquier lectura de una ubicación.Cada hora; cada pocos minutos mientras haya una verificación en curso.failure_type (rejected frente a transient) y la cadena reason. Las desconexiones y reconexiones intermitentes son invisibles: una conexión que se perdió y se restableció aparece como conectada al consultarla.
interaction.*GET /locations/{locationId}/reviews por ubicación, GET /rollup_interactions para varias ubicaciones, GET /reviewDetails por id.Cada 15 minutos para las reseñas nuevas y a diario para una revisión más amplia.El hecho de que algo haya cambiado. Los campos de interacción documentados no incluyen updated-at, por lo que una reseña modificada tiene exactamente el mismo aspecto que una que ya has almacenado: compara los campos que conservas.
local_post.*GET /locations/{locationId}/posts, GET /locations/{locationId}/bulk-posts, GET /posts/{postId} para consultar el estado actual de publicación por sitio.Cada hora mientras haya publicaciones en curso.rejection_reason tal como se entregó y la secuencia de estados por los que pasó un elemento de publicación. Las lecturas te proporcionan el estado actual, no las transiciones.
review_analytics.*GET /locations/{locationId}/review-analytics-overview con los valores de startDate y endDate del período.A diario después del intervalo de agregación y semanalmente para la familia semanal.Las cifras en el momento de la instantánea. El resumen se recalcula en el momento de la lectura, por lo que una reseña que llega tarde o se elimina hace que difiera de lo que contenía la instantánea.

Resultados de entrega

Cada intento, incluso si no llega a enviarse, se registra con un estado. Estas son las cadenas exactas.

Los cuatro resultados y las cadenas de estado que los registran. «Bloqueado» y «Límite de solicitudes excedido» significan que no se realizó ninguna solicitud HTTP, por lo que conviene distinguirlos de un fallo real.
EstadoResultadoQué significaQué hacer
successEntregadoTu endpoint devolvió un 2xx.Nada.
un código HTTP como cadena, e.g. "404", "500"FallidoTu endpoint respondió con ese código distinto de 2xx. 3xx también se incluye aquí, porque no se siguen las redirecciones.Comprueba la ruta, la cadena de redirecciones y la tasa de errores de tu manejador.
999FallidoSe recibió una respuesta sin un código de estado utilizable.Suele deberse a un proxy o un balanceador de carga situado delante de tu endpoint.
timeoutFallidoNo se recibió ninguna respuesta dentro del plazo de 5 segundos.Procesa el trabajo fuera del ciclo de la solicitud y confirma la recepción antes.
network_errorFallidoNo se pudo resolver el nombre del host o conectar con él.Comprueba el DNS y que el nombre del host se pueda resolver públicamente.
errorFallidoEl intento produjo un error distinto de los anteriores, como un fallo en la negociación TLS.Comprueba tu cadena de certificados y los registros del servidor.
blocked_urlBloqueadoLa URL no superó la comprobación de seguridad al intentar conectar: no usa HTTPS, no tiene host o se resolvió a una dirección privada, de loopback, de enlace local o de metadatos.Configura la URL para que apunte a un host HTTPS público. Ten en cuenta que esta comprobación se repite en cada entrega, por lo que un cambio de DNS puede hacer que se empiece a bloquear una URL que antes funcionaba.
endpoint_unverifiedBloqueadoLa cuenta tiene un secreto de firma, pero el endpoint no ha superado la verificación o la verificación se ha restablecido.Vuelve a ejecutar Verificar endpoint desde el panel.
rate_limitedLímite de solicitudes excedidoSe superó el límite por minuto o por día. El evento se descartó.Reduce el volumen o cambia a un plan con un límite mayor. Consulta planes y límites.

Lista de comprobación para el receptor

  • Verifica la firma de cada solicitud; devuelve 401 y no proceses nada si no coincide.
  • Calcula el hash de los bytes del cuerpo sin procesar, nunca de un objeto vuelto a serializar.
  • Devuelve 2xx en mucho menos de 5 segundos; pon el trabajo en cola.
  • Usa los identificadores incluidos en data como claves de cada escritura del manejador para que una repetición produzca el mismo resultado, pero nunca interpretes un identificador repetido como una indicación de que no hay nada que hacer.
  • No deduzcas el orden a partir del orden de llegada ni ordenes por timestamp.
  • Normaliza location_id a una cadena al recibirlo.
  • Ignora los valores de event que no reconozcas y devuelve 200 de todos modos.
  • Sirve la URL de webhooks como URL final, sin redirecciones.
  • Concilia los datos periódicamente con la REST API; no hay reintentos que permitan recuperarlos.