En esta página
Eventos del sitio web y del motor de reservas
Indica a GuestMaker qué hacen los visitantes en un sitio web o motor de reservas, como dejar una reserva a medias, e inicia un recorrido a partir de esos eventos. Un contrato, tres vías de entrada y cada cuenta define los eventos que le interesan.
Vista general
Un motor de reservas sabe cuándo se va un huésped; el hotel quiere escribirle mientras aún tiene el viaje en mente. Los eventos conectan ambos sistemas. Tu sitio web o motor de reservas envía un evento, se identifica al huésped como contacto y se inicia un recorrido que tenga ese evento como activador.
YOUR SIDE GUESTMAKER
Server API POST /api/v1/website-events --+
Webhook POST .../hooks/gme_wh_... --+--> one contract --> contact + consent evidence
Browser SDK gm-events.js --+ |
+-> journey trigger --> wait --> email / WhatsApp
|
+--> stops when the booking completesNo hay que registrar nada antes. Envía un evento con cualquier nombre válido y aparecerá en el desplegable del editor de recorridos en cuanto se reciba, junto a los eventos estándar siguientes. Cada fuente (un sistema que envía eventos) tiene su propia clave, por lo que puedes pausarla o sustituirla sin modificar las demás.
Disponibilidad
Los eventos del sitio web y del motor de reservas se activan por cuenta. Si Ajustes → Integraciones → Eventos del sitio web y del motor de reservas indica que la función no está activada en tu cuenta, consulta con tu contacto de GuestMaker.
Inicio rápido
- En GuestMaker, abre Ajustes → Integraciones → Eventos del sitio web y del motor de reservas, elige Nueva fuente, selecciona Servidor y copia la clave secreta. Solo se muestra una vez.
- Envía tu primer evento. Añade
"test": truepara validarlo y registrarlo sin modificar ningún huésped. - En la fuente, abre el Registro de entregas: allí encontrarás el evento y su resultado. Corrige cualquier problema señalado.
- En un recorrido, elige el activador Evento del sitio web o del motor de reservas, selecciona Carrito abandonado, añade una espera y un correo electrónico, y actívalo.
curl -X POST 'https://www.guestmaker.ai/api/v1/website-events' \
-H 'Authorization: Bearer gme_sk_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"event":"cart.abandoned","idempotency_key":"cart_8841","contact":{"email":"ana@example.com","first_name":"Ana","language":"es"},"consent":{"email_marketing":true,"text":"I agree to receive offers from this hotel by email","captured_at":"2026-09-30T10:11:58Z","language":"en","form_id":"checkout-details"},"context":{"hotel_code":"HD-MAD","stage":"payment","check_in":"2026-11-20","check_out":"2026-11-23","adults":2,"room_name":"Deluxe","value":612.4,"currency":"EUR","cart_id":"8841","resume_url":"https://book.example.com/resume?cart=8841"}}'Una solicitud correcta devuelve 202 con un resultado por evento. El ejemplo anterior es un carrito abandonado real con pruebas de consentimiento; añade "test": true para probarlo de forma segura.
{
"received": 1,
"accepted": 1,
"duplicate": 0,
"rejected": 0,
"results": [
{
"index": 0,
"status": "accepted",
"code": "accepted",
"message": "Accepted",
"event_id": "9b0e…",
"contact": "matched",
"warnings": []
}
]
}Elige una vía de entrada
Las tres vías aceptan los mismos eventos y aplican las mismas reglas. Elige según las capacidades de tu sistema.
API de servidor
Tu backend o motor de reservas puede llamar a una API. Es la vía más fiable: eliges exactamente cuándo enviar y puedes reintentar.
- Credencial:
- Una clave secreta, gme_sk_…, en la cabecera Authorization.
- Envías:
- POST /api/v1/website-events con un cuerpo JSON en nuestro formato.
- Protección:
- Guarda la clave en tu servidor. Si se filtra, revócala en Ajustes y añade una nueva; el resto sigue igual.
Webhook
Un motor de reservas solo puede enviar su propio JSON a una URL. Mapeas sus campos a los nuestros una vez, en Ajustes, y compruebas el resultado con una solicitud real.
- Credencial:
- Una URL privada, …/hooks/gme_wh_…, opcionalmente con una cabecera de firma HMAC.
- Envías:
- POST a la URL con el JSON del motor. Sin código en ninguno de los dos lados.
- Protección:
- La URL es la credencial, así que compártela solo con el motor. Añade un secreto de firma si el motor puede firmar.
SDK para navegador
Tu web o página de reservas ejecuta JavaScript que controlas. Es la única vía que detecta cuando el visitante cierra la pestaña, momento en el que se determina el abandono del carrito.
- Credencial:
- Una clave pública, gme_pk_…, que solo funciona desde las webs que indiques.
- Envías:
- El SDK envía desde el navegador del visitante. También funciona desde Google Tag Manager.
- Protección:
- La clave puede publicarse: solo funciona desde tus webs permitidas y no puede leer ningún dato.
La clave del navegador es pública por diseño
La clave gme_pk_… está pensada para incluirse en tu página. Es seguro porque solo funciona desde los sitios web que indiques en la fuente (un origen exacto, como https://book.example.com, sin comodines), solo permite enviar eventos y tiene límites por visitante. Una clave secreta gme_sk_… nunca debe aparecer en una página web ni en una aplicación móvil.
El evento
El evento
Un evento es un objeto JSON. Envíalo solo o envía hasta 50 como { "events": [ … ] }. Las claves desconocidas se rechazan en lugar de ignorarse: de lo contrario, una errata como `consents` se aceptaría sin registrar nada.
| Campo | Tipo | Descripción |
|---|---|---|
| event * | string | Qué ocurrió: minúsculas, segmentos separados por puntos, de 1 a 4 segmentos y hasta 64 caracteres. Usa un nombre estándar si encaja; cualquier otro nombre válido es un evento personalizado. Ejemplo: cart.abandoned |
| occurred_at | string (ISO 8601 with offset) | Cuándo ocurrió. Por defecto, se usa la hora de recepción. No puede tener más de 7 días de antigüedad ni adelantarse más de 5 minutos. Ejemplo: 2026-11-02T18:04:11Z |
| idempotency_key | string | Tu propio identificador del evento, de hasta 200 caracteres. Enviar de nuevo la misma clave no produce cambios, así que puedes reintentar con seguridad. Sin ella, el mismo evento para el mismo carrito o sesión del mismo huésped en un minuto se considera duplicado. Ejemplo: cart_8841 |
| contact | object | Quién era. Un evento sin email ni teléfono se contabiliza, pero no puede iniciar un journey: no hay nadie a quien enviar un mensaje. |
| consent | object | Prueba de que el visitante aceptó recibir emails comerciales. Sin ella, se crea el contacto, pero no se envían emails comerciales. |
| context | object | La reserva en curso: hotel, fechas, huéspedes, habitación e importe. |
| properties | object | Otros datos que quieras conservar, hasta 25 claves. Los nombres usan letras minúsculas, dígitos y guiones bajos; los valores son cadenas de hasta 500 caracteres, números o booleanos. Ejemplo: { "promo": "SPRING" } |
| test | boolean | Valida y registra el evento, pero no inicia ningún journey ni escribe contactos. Úsalo mientras configuras la integración. |
contact
El huésped se vincula a un contacto existente por email o teléfono, o se crea si es nuevo. Una dirección de agencia, un marcador de posición o un buzón compartido de un departamento no se acepta como persona, por lo que una dirección intermediaria de OTA nunca se convierte en un contacto comercial.
| Campo | Tipo | Descripción |
|---|---|---|
| string | Se convierte automáticamente a minúsculas. Es obligatorio, con o sin teléfono, para un journey de email. Ejemplo: ana@example.com | |
| phone | string | Formato internacional, con dígitos y un + inicial opcional. Ejemplo: +34600111222 |
| first_name | string | Hasta 100 caracteres. |
| last_name | string | Hasta 100 caracteres. |
| language | string | ISO 639-1, opcionalmente con región: es, pt-BR. El journey escribe al huésped en este idioma si dispone de una plantilla correspondiente. Ejemplo: es |
consent
El consentimiento requiere pruebas; de lo contrario, se ignora. `email_marketing: true` es solo una declaración: se acepta cuando incluye `text` (la frase exacta mostrada) y un `captured_at` reciente. `false` no escribe nada, y ningún evento puede volver a suscribir a un huésped que se haya dado de baja.
| Campo | Tipo | Descripción |
|---|---|---|
| email_marketing * | boolean | true declara que el visitante marcó la casilla; false no registra nada. Ejemplo: true |
| text | string | El texto exacto que aceptó el visitante, entre 10 y 2000 caracteres. Ejemplo: I agree to receive offers from this hotel by email |
| captured_at | string (ISO 8601 with offset) | Cuándo aceptó. No puede tener más de 7 días de antigüedad: el consentimiento se registra cuando se otorga, nunca se vuelve a registrar después. |
| language | string | El idioma en que se mostró la frase. |
| form_id | string | El formulario o paso que lo recogió, para tu propio registro de auditoría. Ejemplo: checkout-details |
context
Qué estaba haciendo el visitante. Todos los campos son opcionales; el journey puede usar en sus mensajes los que envíes.
| Campo | Tipo | Descripción |
|---|---|---|
| hotel_id | string (UUID) | Un ID de hotel de Ajustes → Propiedades. |
| hotel_code | string | Tu propio código de hotel, si la propiedad tiene uno. Ejemplo: HD-MAD |
| hotel_name | string | Se busca por nombre entre los hoteles del grupo cuando no se envía un ID ni un código. |
| stage | one of dates · room · extras · details · payment | En qué punto de la reserva estaba el visitante. En cart.abandoned es el paso en el que abandonó; el journey puede limitarse a determinadas etapas. Ejemplo: payment |
| check_in | string (YYYY-MM-DD) | Una fecha real del calendario. Ejemplo: 2026-11-20 |
| check_out | string (YYYY-MM-DD) | Posterior a check_in. Ejemplo: 2026-11-23 |
| adults | integer 0–30 | |
| children | integer 0–30 | |
| rooms | integer 1–20 | |
| room_name | string | La habitación o tipo de habitación elegido. Ejemplo: Deluxe |
| rate_name | string | La tarifa elegida. |
| value | number | El importe del carrito. Ejemplo: 612.4 |
| currency | string (ISO 4217, capitals) | Ejemplo: EUR |
| cart_id | string | Tu identificador de la reserva en curso. El journey lo usa para distinguir un carrito de otro y detenerse cuando ese carrito completa la reserva. Ejemplo: 8841 |
| session_id | string | La sesión del visitante, cuando aún no hay un ID de carrito. |
| resume_url | string (https) | Dónde puede retomar la reserva el huésped. Se incluye en el email, por lo que debe ser un enlace https. Ejemplo: https://book.example.com/resume?cart=8841 |
| page_url | string (https) | La página en la que estaba el visitante. |
| referrer | string (https) | De dónde llegó. |
* indica un campo obligatorio. Los demás son opcionales; los campos que no figuren en el contrato se rechazan, en lugar de ignorarse.
Eventos estándar
Estos eventos tienen su propio apartado al principio del desplegable del editor de recorridos. Cualquier otro nombre en minúsculas y separado por puntos (por ejemplo, custom.spa_viewed) es un evento personalizado: envíalo y podrás seleccionarlo una vez recibido.
| Evento | Significado |
|---|---|
| cart.abandoned | A visitor left mid-booking without completing. `context.stage` says where: dates, room, extras, details or payment. |
| lead.captured | A visitor left their email or phone (a form, a price alert, a callback request). |
| search.performed | A visitor searched availability for dates. |
| room.selected | A visitor picked a room or rate. |
| checkout.started | A visitor reached the guest-details step. Pair it with a Wait and an exit on `checkout.completed` to build an abandoned-cart flow without the vendor timing anything. |
| payment.failed | A payment attempt failed and the booking was not completed. |
| checkout.completed exit | The visitor completed the booking. Stops any journey waiting on this visitor's cart. |
En cart.abandoned, context.stage indica en qué punto abandonó el huésped: dates, room, extras, details, payment. Puedes limitar un recorrido a determinadas etapas, por ejemplo, a huéspedes que llegaron al pago.
API de servidor
Envía POST /api/v1/website-events con Authorization: Bearer gme_sk_… y un cuerpo JSON: un evento, o { "events": [ … ] } con hasta 50. En un lote, cada evento se evalúa por separado: la solicitud se acepta y cada evento tiene su propio resultado, por lo que un evento incorrecto no provoca el rechazo de los demás.
Idempotencia y reintentos
Asigna a cada evento un idempotency_key. Al enviar de nuevo la misma clave, la respuesta es duplicate y no se modifica nada, por lo que puedes reintentarlo. Un evento que ningún recorrido activo esté esperando se cuenta, pero no se almacena; si se repite, vuelve a responder accepted y tampoco se modifica nada. Reintenta ante 429 (espera los segundos que indique Retry-After) y ante 5xx. Cualquier otro 4xx seguirá fallando si envías los mismos bytes.
Un evento individual que no supera la validación
Devuelve 422, con los campos y sus problemas en results[0].issues. El mismo evento dentro de un lote se indica en una respuesta 202.
{
"received": 1,
"accepted": 0,
"duplicate": 0,
"rejected": 1,
"results": [
{
"index": 0,
"status": "rejected",
"code": "invalid_event",
"message": "The event does not match the contract",
"event_id": null,
"contact": "none",
"warnings": [],
"issues": [
{
"field": "context.check_out",
"message": "check_out must be after check_in"
}
]
}
]
}El evento completo
{
"event": "cart.abandoned",
"idempotency_key": "cart_8841",
"contact": {
"email": "ana@example.com",
"first_name": "Ana",
"language": "es"
},
"consent": {
"email_marketing": true,
"text": "I agree to receive offers from this hotel by email",
"captured_at": "2026-09-30T10:11:58Z",
"language": "en",
"form_id": "checkout-details"
},
"context": {
"hotel_code": "HD-MAD",
"stage": "payment",
"check_in": "2026-11-20",
"check_out": "2026-11-23",
"adults": 2,
"room_name": "Deluxe",
"value": 612.4,
"currency": "EUR",
"cart_id": "8841",
"resume_url": "https://book.example.com/resume?cart=8841"
}
}La descripción legible por máquinas está en el documento OpenAPI, generado a partir de las mismas definiciones que usa la API para validar, por lo que coincide con lo que acepta el servidor.
Webhook y asignación de campos
Muchos motores de reservas solo pueden enviar su propio JSON a una URL mediante POST. Crea un Webhook como fuente, da al motor la URL privada e indica a GuestMaker una vez cómo se corresponden sus campos con los nuestros. Sin código en ninguno de los sistemas.
POST https://www.guestmaker.ai/api/v1/website-events/hooks/gme_wh_…?test=1
Content-Type: application/json
{
"type": "CartAbandoned",
"step": "PAYMENT",
"user": {
"mail": "ana@example.com",
"name": "Ana"
},
"cart": {
"id": 8841,
"total": "612,40",
"currency": "eur"
},
"stay": {
"in": "20/11/2026",
"out": "23/11/2026"
}
}El editor de asignaciones
En «Configura la asignación de campos» de la fuente, GuestMaker abre la última solicitud enviada por el motor (con los datos personales enmascarados), muestra sus campos para elegirlos y previsualiza el evento tal como lo construirá el servidor. Cada fila toma un valor de un campo del payload, un valor fijo o una plantilla, como cart_{{cart.id}}. Transformaciones disponibles: lower, upper, trim, number, boolean, date, datetime. Un mapa de términos traduce el vocabulario del motor, por ejemplo, CartAbandoned → cart.abandoned.
La asignación traduce los datos y respeta el contrato
El resultado de la asignación pasa por el mismo contrato estricto que cualquier otro evento, por lo que no puede generar datos que la API rechazaría. Ofrece rutas, valores fijos, plantillas y una lista cerrada de transformaciones, sin lenguaje de scripting. Si el payload es un array, se trata como un lote de hasta 50.
Checking the URL
Muchos motores de reservas y herramientas de webhook comprueban una URL mediante GET antes de guardarla. Un GET a la URL del webhook no envía ningún evento: devuelve 200 con { "ready": true, "signature_required": … } cuando se aceptaría un POST; en caso contrario, devuelve el mismo 401 o 403 que recibiría un POST. Así se distingue una fuente pausada de un token mal escrito.
Firma
Si el motor puede firmar sus solicitudes, configura un secreto de firma en la fuente. Cada solicitud debe incluir X-GM-Signature: t=<unix seconds>,v1=<hex>, donde v1 es el HMAC-SHA256 de "<t>.<raw body>" con ese secreto, la construcción que usa Stripe. Firma los bytes exactos que envíes y usa una marca de tiempo con un margen de 5 minutos respecto al reloj del servidor. Durante la rotación de un secreto puedes enviar dos valores de v1.
import { createHmac } from 'node:crypto'
const t = Math.floor(Date.now() / 1000)
const v1 = createHmac('sha256', SIGNING_SECRET).update(`${t}.${rawBody}`).digest('hex')
// rawBody is the EXACT string you send. Then add this header to the request:
const signature = `t=${t},v1=${v1}` // X-GM-Signature: t=1767225600,v1=5257a869…Sin un secreto, la URL privada es la credencial: compártela solo con el motor y revócala en la fuente si se filtra.
SDK para navegador
/sdk/gm-events.js es un script pequeño, sin dependencias (menos de 6 KB comprimido), que no crea cookies. Envía eventos desde el navegador del visitante y es la única vía que detecta cuándo un huésped cierra la pestaña, el momento en que se determina que un carrito ha quedado abandonado.
<script async src="https://www.guestmaker.ai/sdk/gm-events.js"></script>
<script>
window.gme = window.gme || function () { (gme.q = gme.q || []).push(arguments) }
gme('init', { key: 'gme_pk_YOUR_PUBLIC_KEY' /* , hotelCode: 'HD-MAD' */ })
// The moment the guest gives an email or phone in your booking form:
// gme('identify', { email: 'ana@example.com' })
// Report the booking when the guest leaves without finishing it:
gme('abandonOnLeave', {
getCart: function () {
// Return the booking in progress, or null when there is none.
return { cart_id: 'YOUR-CART-ID', stage: 'payment', check_in: '2026-11-20', check_out: '2026-11-23', value: 612.4, currency: 'EUR' }
}
})
// When the booking is paid:
// gme('completed', { cart_id: 'YOUR-CART-ID' })
</script>| Comando | Qué hace |
|---|---|
init | Inicia con tu clave pública. Opciones: hotelCode, hotelId, debug, persistContact, waitForConsent, endpoint. |
track | Send any event: gme('track', 'search.performed', { context: { adults: 2 } }). |
identify | Identifica al huésped cuando facilite un correo electrónico o teléfono. Se conserva solo en la pestaña (sessionStorage), para reconocerlo aunque la reserva tenga varias páginas. |
abandonOnLeave | Proporciona una función getCart. Cuando el huésped abandona la página con un carrito y un correo electrónico o teléfono, envía cart.abandoned una vez por carrito y etapa. |
completed | La reserva se ha pagado: envía checkout.completed y deja de notificar ese carrito como abandonado. |
allow / deny | Responde a tu aviso de cookies. Con waitForConsent: true, no se envía ni almacena nada hasta ejecutar allow(). |
flush / reset | Envía ahora lo que está en cola / olvida al huésped y empieza de nuevo (por ejemplo, al cerrar sesión). |
Casillas de consentimiento
identify(contact, { consent: { email_marketing: true, text, form_id } }) registra una declaración de consentimiento con la frase que pases y la hora en que se marcó la casilla. Sin text, la selección se envía, pero el servidor la ignora y no se envían correos de marketing.
Google Tag Manager
Pega el fragmento del SDK para navegador anterior en una etiqueta HTML personalizada que se active en tus páginas de reservas. Mantén el carrito en una variable global que defina tu sitio y llama a gme('identify', …) y gme('completed', …) desde otras etiquetas.
Qué ocurre cuando algo falla
No provoca excepciones en tu página ni la bloquea. Ante un error de red, 429 o 5xx, reintenta hasta cinco veces con espera progresiva (1, 2, 4 y 8 segundos) y respeta Retry-After. Un 4xx se indica una vez en la consola con el motivo del servidor y no se reintenta. Los eventos registrados durante la espera de un reintento se ponen en cola detrás de él, para mantener el orden. Pueden esperar hasta 50 eventos; a partir de ahí, se descarta el más antiguo.
Consentimiento y cumplimiento
Son reglas de la plataforma, se aplican a todas las vías de entrada y no se pueden configurar.
- Sin prueba de consentimiento no se envían correos de marketing. Se crea el huésped a partir del carrito abandonado y se inicia el recorrido, pero se omite el paso de correo electrónico, con un motivo visible para la cuenta, salvo que el contacto tenga consentimiento de marketing.
- El consentimiento debe acreditarse para que se acepte. Solo se acepta
consent.email_marketing: truecon eltextexacto que aceptó el huésped y uncaptured_atde los últimos 7 días. GuestMaker registra la frase, la hora, el idioma, el formulario y la IP y el agente de usuario de la solicitud en el registro de auditoría del consentimiento. - Un evento nunca puede volver a suscribir a un huésped que se haya dado de baja. Si el contacto se ha dado de baja o está excluido, se ignora la declaración de consentimiento y la respuesta lo indica (
consent_ignored_opted_out). Una casilla premarcada por error no puede anular una baja. - Nunca es transaccional. Un recordatorio a un huésped que dejó una reserva a medias es un mensaje comercial. Los recorridos iniciados por un evento web envían correos en la categoría de marketing; todas las plantillas de WhatsApp que envíen requieren consentimiento de marketing, sea cual sea la categoría en la que se registraron.
- Solo personas. Un mostrador de agencia, un dato de relleno o una cuenta genérica no se aceptan como persona, por lo que una dirección intermediaria de OTA nunca se convierte en contacto de marketing.
La base jurídica sigue siendo tu responsabilidad
GuestMaker exige las pruebas de consentimiento. El texto de tu formulario de reserva y la base jurídica que utilices son tu responsabilidad y debe aprobarlos tu delegado de protección de datos. La configuración predeterminada es deliberadamente estricta.
Úsalo en un recorrido
Elige el activador Evento del sitio web o del motor de reservas (categoría Sitio web y motor de reservas). El desplegable lista primero los eventos estándar y después los personalizados recibidos por tu cuenta, marcados como «aún no recibido» hasta que llegue uno.
| Opción | Qué hace |
|---|---|
| Evento | El evento que inicia el recorrido, por ejemplo cart.abandoned. |
| Origen | Solo lo inician los eventos de una fuente. Déjalo vacío para aceptar cualquier fuente. |
| Etapas | Para cart.abandoned: solo carritos que se detuvieron en estas etapas. Vacío significa cualquier etapa. |
| Detener cuando | Events that end the journey for the same cart or session while it is running. Default: checkout.completed. Empty means never stop on an event. |
| Detener con una reserva real | Una reserva recibida de tu PMS o CRS también lo termina. Activado por defecto. |
Un flujo habitual de carrito abandonado: actívalo con cart.abandoned (o checkout.started), espera 30 minutos y envía el correo de recordatorio con un botón al enlace que permite al huésped retomar la reserva. Si mientras tanto el huésped completa la reserva, checkout.completed para el mismo carrito termina el recorrido antes de enviar el correo.
Usar el evento en un mensaje
Los datos del evento pueden usarse en el correo de un recorrido como etiquetas de combinación y en una plantilla de WhatsApp como variables. Ambos leen el evento que inició esa ejecución del recorrido.
Etiquetas de combinación de correo electrónico
Escribe la etiqueta en el correo electrónico o selecciónala en Evento web en el selector de etiquetas de combinación. Las fechas se escriben en el idioma del huésped («20 de noviembre de 2026» para un huésped hispanohablante) y los importes con el formato de su moneda. Si el evento no incluye un valor, se muestra texto vacío; adapta el texto que lo rodea para que se lea bien sin él.
| Etiqueta | Contenido |
|---|---|
| {{event_resume_url}} | Resume booking link, por ejemplo https://book.example.com/resume?cart=abc123 |
| {{event_hotel_name}} | Hotel they were booking, por ejemplo Hotel Mar Azul |
| {{event_check_in}} | Check-in they chose, por ejemplo November 20, 2026 |
| {{event_check_out}} | Check-out they chose, por ejemplo November 23, 2026 |
| {{event_room_name}} | Room they looked at, por ejemplo Deluxe Sea View |
| {{event_rate_name}} | Rate they looked at, por ejemplo Alojamiento y desayuno |
| {{event_value}} | Cart value, por ejemplo €450.00 |
| {{event_currency}} | Cart currency, por ejemplo EUR |
| {{event_adults}} | Adults, por ejemplo 2 |
| {{event_children}} | Children, por ejemplo 0 |
| {{event_rooms}} | Habitaciones, por ejemplo 1 |
| {{event_stage}} | Where they left, por ejemplo payment |
| {{event_name}} | Nombre del evento, por ejemplo cart.abandoned |
| {{event_prop_<name>}} | Una propiedad personalizada que enviaste en properties, por ejemplo {{event_prop_spa_package}}. |
Si falta el enlace para retomar la reserva, nunca se envía un botón vacío
Si el correo usa {{event_resume_url}} y el evento no incluía context.resume_url, GuestMaker omite ese correo y registra el motivo. Así no envía un botón sin destino. Envía resume_url en todos los eventos de carrito a los que quieras dar seguimiento.
Declara tus propios eventos de antemano
Un evento personalizado suele aparecer en el desplegable del activador cuando tu sitio web lo ha enviado. Para crear el recorrido antes de terminar la integración, decláralo en Ajustes → Integraciones → Eventos web → Eventos que pueden iniciar un recorrido. Aparece como «aún no recibido» y el primer evento real se registra en él. Un evento declarado que no se haya enviado puede eliminarse; uno recibido permanece en el historial. Los nombres usan minúsculas, dígitos y guiones bajos con puntos entre las partes y deben coincidir con lo que envía tu sitio web.
Variables de WhatsApp
| Ruta | Contenido |
|---|---|
| event.payload.context.resume_url | Dónde puede retomar la reserva el huésped. |
| event.payload.context.check_in | Fecha de llegada. |
| event.payload.context.check_out | Fecha de salida. |
| event.payload.context.room_name | La habitación que estaba consultando. |
| event.payload.context.value | El importe del carrito. |
| event.payload.properties.<name> | Cualquier propiedad que hayas enviado. |
Los eventos solo se almacenan cuando hay un huésped identificado y un recorrido activo que los espera. Enviar un evento que ningún recorrido espera no consume recursos ni crea datos.
Errores y resultados
Los errores de solicitud devuelven { error: { code, message, detail?, fix, docs } } y el estado HTTP indicado. El enlace docs apunta a la entrada siguiente y fix indica qué cambiar.
Rechazado: corrige la solicitud
The event was not accepted. Request-level errors answer the whole request with the HTTP status shown; the others are reported per event.
- Motivo:
- The request body could not be parsed as JSON.
- Solución:
- Send a JSON body with Content-Type: application/json (or text/plain from navigator.sendBeacon).
- Motivo:
- The body is not one event object and not { "events": [...] }.
- Solución:
- Send one event object, or an object with an "events" array.
- Motivo:
- The request body exceeds the limit: 64 KB, or 256 KB on a webhook.
- Solución:
- Send fewer events per request, or trim `properties`.
- Motivo:
- More than 50 events in "events".
- Solución:
- Split the batch into requests of at most 50 events.
- Motivo:
- "events" is an empty array.
- Solución:
- Send at least one event.
- Motivo:
- The event source was paused or deleted.
- Solución:
- Re-enable the source in Settings, or use another one.
- Motivo:
- The tenant does not have website events enabled.
- Solución:
- Ask your GuestMaker contact to enable Website & booking engine events.
- Motivo:
- A browser key was used from a website that is not in the source's allowed origins.
- Solución:
- Add the site's origin (for example https://www.example.com) to the source's allowed origins.
- Motivo:
- The source requires signed webhooks and no X-GM-Signature header was sent.
- Solución:
- Sign the request: X-GM-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">.
- Motivo:
- The signature is malformed, was computed over different bytes, or used the wrong secret.
- Solución:
- Sign the exact raw body bytes with the source's signing secret; do not re-serialise the JSON before signing.
- Motivo:
- The timestamp in the signature is more than 5 minutes from the server's clock.
- Solución:
- Sign with the current time and check the sender's clock.
- Motivo:
- The source exceeded its per-minute limit.
- Solución:
- Retry after the number of seconds in the Retry-After header; batch events instead of sending one request each.
- Motivo:
- An unexpected failure while processing the request.
- Solución:
- Retry with the same idempotency_key; the event will not be duplicated.
- Motivo:
- A field is missing, has the wrong type or format, or is not part of the contract.
- Solución:
- Read the `issues` list: each entry names the field and the problem.
- Motivo:
- The source has an allow-list of events and this name is not on it.
- Solución:
- Add the event to the source's allowed events, or send an allowed one.
- Motivo:
- The source is limited to specific hotels and this event names another.
- Solución:
- Send the hotel the source is allowed for, or widen the source's hotels.
- Motivo:
- hotel_id, hotel_code or hotel_name matches none of the tenant's hotels.
- Solución:
- Use a hotel_id from Settings → Properties, or a hotel code that exists.
- Motivo:
- occurred_at tiene más de 7 días de antigüedad.
- Solución:
- Send events when they happen. Older data belongs in an import, not the live events stream.
- Motivo:
- occurred_at is more than 5 minutes ahead of the server clock.
- Solución:
- Check the sender's clock, or omit occurred_at to use the receive time.
Aceptado, pero parte de lo previsto no se ha realizado
El evento se ha aceptado y la respuesta indica qué se ha ignorado, de modo que ninguna declaración se descarta sin avisar.
- Motivo:
- consent.email_marketing was true but consent.text (the exact sentence shown) and/or a valid captured_at were missing or stale.
- Solución:
- Send the exact wording the visitor agreed to and when. Without it the contact is created but marketing email will not send.
- Motivo:
- The contact previously unsubscribed or is suppressed. A website event can never re-subscribe someone.
- Solución:
- Nothing to fix on your side: the guest must opt in again through an email preference link.
- Motivo:
- consent.captured_at is more than 7 days old. Consent must be recorded when the guest gives it, not replayed later.
- Solución:
- Send the consent block in the same request that follows the guest ticking the box, with captured_at set to that moment.
- Motivo:
- The source is set to ignore consent, so a consent claim on its events is never written to the contact.
- Solución:
- Switch the source's consent mode to 'evidence required' if this website really collects marketing consent.
- Motivo:
- The event has no contact.email or contact.phone, so there is nobody to message.
- Solución:
- Include contact.email (or phone) on events you want to start a journey. Anonymous events are still counted in the catalogue.
Resultados habituales
Resultados de cada evento dentro de una respuesta 202.
- Motivo:
- The event was stored and, if a journey listens for it, queued.
- Solución:
- No hay nada que hacer.
- Motivo:
- Ya se ha aceptado un evento con la misma clave de idempotencia.
- Solución:
- Nothing to do; retries are safe.
- Motivo:
- test era true: el evento se registra, pero no inicia recorridos ni escribe contactos.
- Solución:
- Remove test: true to send for real.
- Motivo:
- La dirección es de un mostrador de agencia, un dato de relleno o una cuenta genérica, por lo que no se ha creado ningún contacto.
- Solución:
- Nothing to fix unless the address is a real guest; see the identity rules in Settings.
Límites
| Límite | Valor |
|---|---|
| Cuerpo de la solicitud | 64 KB (256 KB for a webhook, which carries the engine's whole payload) |
| Eventos por solicitud | 50 |
| Nombre del evento | lowercase, dotted, 1–4 segments, 64 characters |
| Clave de idempotencia | 200 characters |
| Hoteles | 25 keys, string values up to 500 characters |
| occurred_at | up to 7 days old, at most 5 minutes ahead |
| captured_at del consentimiento | up to 7 days old |
| Tarifa | 600 requests a minute per source by default (adjustable per source); a browser key is also limited per visitor |
| Claves por fuente | dos claves activas a la vez, para sustituir una clave sin interrupciones |
| Registro de entregas | 14 días |
Pruebas
- Añade
"test": true(o?test=1en una URL de webhook): el evento se valida y registra, sin iniciar recorridos ni escribir contactos. - Enviar evento de prueba en una fuente envía un evento desde el panel, para comprobar la conexión antes de que el motor esté listo.
- El Registro de entregas muestra cada solicitud con su resultado, el motivo del rechazo y un enlace para corregirlo. Los datos personales aparecen enmascarados.
- Una solicitud con clave ausente o incorrecta no puede asociarse a ninguna fuente y no aparece en ningún registro de entregas: el remitente ve el
401y la cuenta no ve nada. Si un proveedor indica que los eventos no llegan, empieza comprobando la clave con una solicitud que incluya"test": true. - Para una fuente de navegador, añade
debug: trueuninitpara ver en la consola lo que envía el SDK.
Proveedores de motores de reservas
Si eres proveedor de un motor de reservas, no necesitas una integración con nosotros: tu cliente crea una fuente Webhook, asigna tus campos una vez y tú envías a su URL mediante POST. Este es el mensaje que un hotel puede enviarte.
Subject: Send booking events to GuestMaker
Hello,
We use GuestMaker to follow up with guests who leave a booking half-finished. Please send these events
from our booking engine to the webhook URL below, as JSON over HTTPS (POST):
1. CartAbandoned – the guest left before paying (please include which step they were on)
2. BookingPaid – the guest completed the booking (so we stop following up)
Webhook URL: https://www.guestmaker.ai/api/v1/website-events/hooks/gme_wh_YOUR_WEBHOOK_TOKEN
Signing: optional. If you can sign requests, we will give you a secret; the header is
X-GM-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
Each payload should carry, in your own field names (we map them, you do not need to change anything):
- the guest's email (and phone if you have it)
- the cart or booking id
- the hotel
- check-in and check-out dates, adults, room name, total and currency
- the step reached, and a link that resumes the booking
- if you collect marketing consent: the exact sentence shown and when it was ticked
An example of what you might send:
{
"type": "CartAbandoned",
"step": "PAYMENT",
"user": {
"mail": "ana@example.com",
"name": "Ana"
},
"cart": {
"id": 8841,
"total": "612,40",
"currency": "eur"
},
"stay": {
"in": "20/11/2026",
"out": "23/11/2026"
}
}
If your tool checks a URL before saving it, a GET on the URL answers 200 when it is live.
Please add "?test=1" to the URL for a first request: it is checked and logged but starts nothing.
Questions: developers@guestmaker.ai
Thank you.Novedades
| Versión | Cambio |
|---|---|
| 1.1.0 | Email merge tags from the event that started a journey ({{event_resume_url}}, {{event_check_in}}, {{event_prop_<name>}} and more), declaring custom events in Settings before the first one arrives, and a skip with a recorded reason when an email needs a resume link the event did not carry. |
| 1.0.0 | Primera versión: API de servidor, webhook con asignación de campos y firma opcional, SDK para navegador, activador de evento del sitio web o motor de reservas, pruebas de consentimiento y esta referencia con un documento OpenAPI generado. |