API pública: recibe webhooks de reservas en tiempo real

Actualizado

Los webhooks son la forma más rápida de mantenerte sincronizado con Nowistay. En lugar de consultar la API cada pocos minutos para detectar reservas nuevas o modificadas, le indicas a Nowistay adónde enviar los eventos y tu servicio los recibe en cuanto ocurren. Este artículo te muestra cómo crear un webhook, verificar la firma y gestionar los reintentos.

Recordatorio rápido: qué es la API pública

La API REST pública de Nowistay permite a tus herramientas y a tus socios leer y escribir datos de Nowistay mediante HTTPS. Te autenticas con OAuth2 (código de autorización + PKCE), recibes un token de acceso y llamas a los endpoints bajo /public/v1/*. Los webhooks son la contrapartida push: los mismos datos, con la misma forma que GET /public/v1/bookings/{id}, pero es Nowistay quien te los envía cuando algo cambia.

Si todavía no has configurado OAuth, empieza por la guía principal: Usar la API REST pública de Nowistay. Cubre el registro, el flujo de consentimiento, los scopes y cómo probar las llamadas desde el playground de la documentación.

Qué obtienes

  • Entrega en tiempo real de los eventos booking.created y booking.updated.
  • El mismo cuerpo JSON que GET /public/v1/bookings/{id}, con fechas, canal, estado, importes, campos relevantes para las misiones y tus parámetros de reserva personalizados.
  • Firma HMAC-SHA256 para que puedas verificar que cada petición procede realmente de Nowistay.
  • Reintentos automáticos con retroceso exponencial cuando tu endpoint falla o agota el tiempo de espera.
  • Desactivación automática tras 72 horas de fallos continuados (puedes reactivarlo desde la API).

Antes de empezar

  • Necesitas la suscripción AI Channel Manager en la propiedad. Sin ella, las entregas se omiten.
  • El token OAuth usado para crear el webhook debe incluir el scope webhooks.write. POST /public/v1/webhooks requiere además bookings.read, porque el payload es el detalle completo de una reserva.
  • Tu URL receptora debe ser accesible desde internet público, servida por HTTPS en producción, y responder en menos de 10 segundos con un estado 2xx.
  • Un webhook está ligado a una propiedad. Puedes registrar hasta 3 webhooks por propiedad.

Paso 1. Crea un webhook

curl -X POST https://api.nowistay.com/public/v1/webhooks \
  -H "Authorization: Bearer nis_access_token_example" \
  -H "Content-Type: application/json" \
  -d '{
    "propertyId": 101,
    "url": "https://partner.example.com/nowistay/webhooks",
    "events": ["booking.created", "booking.updated"],
    "description": "PMS sync"
  }'

Respuesta:

{
  "id": 801,
  "propertyId": 101,
  "url": "https://partner.example.com/nowistay/webhooks",
  "events": ["booking.created", "booking.updated"],
  "description": "PMS sync",
  "status": "active",
  "createdAt": "2026-05-27T16:40:00Z",
  "updatedAt": "2026-05-27T16:40:00Z",
  "signingSecret": "whsec_9npwH6J8QyZp1L8U3xqG7sN2"
}

Guarda el signingSecret ahora. Solo se devuelve una vez, en el momento de la creación. No hay forma de volver a leerlo desde la API. Si lo pierdes, elimina el webhook y crea uno nuevo.

Paso 2. Recibe tu primer evento

Cada entrega es un HTTP POST a tu URL, con estas cabeceras:

  • Content-Type: application/json
  • User-Agent: Nowistay-Webhooks/1.0
  • Nowistay-Webhook-Id: 801
  • Nowistay-Webhook-Event-Id: evt_LkP2sxR9zV8QyW... (único por evento)
  • Nowistay-Webhook-Event-Type: booking.updated
  • Nowistay-Webhook-Timestamp: 1779292800 (segundos Unix)
  • Nowistay-Webhook-Signature: v1=3a7b...e1c (HMAC-SHA256 en hexadecimal)

Y un cuerpo JSON con esta forma:

{
  "id": "evt_LkP2sxR9zV8QyW...",
  "type": "booking.updated",
  "createdAt": "2026-05-27T16:40:00Z",
  "apiVersion": "2026-05-27",
  "propertyId": 101,
  "bookingId": 9001,
  "data": {
    "booking": {
      "id": 9001,
      "propertyId": 101,
      "arrival": "2026-07-14",
      "departure": "2026-07-18",
      "status": "confirmed",
      "channel": "airbnb",
      "reservationNumber": "HMABCDE",
      "guest": { "firstName": "Maya", "lastName": "M." },
      "amounts": { "amount": "620.00", "currency": "EUR" },
      "identity": { "status": "verified" }
    }
  }
}

Tu endpoint debe responder con cualquier estado 2xx (lo habitual es 200 o 204). Cualquier otra respuesta, o una respuesta que tarde más de 10 segundos, cuenta como fallo y provoca un reintento.

Paso 3. Verifica la firma

Verifica siempre la firma antes de fiarte del cuerpo. Sin verificación, cualquiera que adivine tu URL podría enviar eventos falsos a tu servicio.

Nowistay calcula la firma así:

signature = "v1=" + HMAC_SHA256(
  signing_secret,
  timestamp + "." + event_id + "." + raw_request_body
).hex()

Vuelve a calcularla por tu parte con las cabeceras Nowistay-Webhook-Timestamp y Nowistay-Webhook-Event-Id y el cuerpo bruto exacto de la petición (no analices ni vuelvas a serializar el JSON antes de verificar, o corres el riesgo de que haya diferencias de bytes).

Ejemplo en Node.js

import crypto from "node:crypto";

function verify(req, signingSecret) {
  const timestamp = req.headers["nowistay-webhook-timestamp"];
  const eventId = req.headers["nowistay-webhook-event-id"];
  const signature = req.headers["nowistay-webhook-signature"];
  const rawBody = req.rawBody; // Buffer of the raw HTTP body

  const signed = Buffer.concat([
    Buffer.from(`${timestamp}.${eventId}.`, "utf8"),
    rawBody,
  ]);
  const expected = "v1=" + crypto
    .createHmac("sha256", signingSecret)
    .update(signed)
    .digest("hex");

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Ejemplo en Python

import hmac, hashlib

def verify(request, signing_secret: str) -> bool:
    timestamp = request.headers["Nowistay-Webhook-Timestamp"]
    event_id = request.headers["Nowistay-Webhook-Event-Id"]
    signature = request.headers["Nowistay-Webhook-Signature"]
    raw_body = request.body  # bytes

    signed = f"{timestamp}.{event_id}.".encode("utf-8") + raw_body
    expected = "v1=" + hmac.new(
        signing_secret.encode("utf-8"),
        signed,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

Comprueba también que la marca de tiempo es reciente (por ejemplo, dentro de los últimos 5 minutos) para protegerte de los ataques de repetición.

Qué incluye (y qué no) el payload

El objeto data.booking refleja lo que devuelve GET /public/v1/bookings/{id} cuando se llama sin el scope bookings.sensitive.read. Es decir:

  • Incluido: fechas, canal, estado, importes, nombre y apellido del huésped, adultos y niños, parámetros de reserva personalizados (si están activados en el webhook).
  • No incluido: correo del huésped, teléfono, dirección, ciudad, código postal, país, idioma y el código de la cerradura inteligente. Se consideran datos sensibles y Nowistay no los envía a los suscriptores de webhooks.

Cuando tu herramienta necesite uno de esos campos sensibles, consulta el detalle de la reserva en la API con un token que incluya el scope bookings.sensitive.read. El bookingId del sobre del webhook es la entrada adecuada para esa llamada.

curl https://api.nowistay.com/public/v1/bookings/9001 \
  -H "Authorization: Bearer nis_access_token_with_sensitive_scope"

Reintentos y desactivación automática a las 72 horas

Si tu endpoint devuelve un estado distinto de 2xx, agota el tiempo de espera o falla por cualquier otro motivo, Nowistay no descarta el evento. Programa un reintento, con retroceso exponencial y algo de jitter:

  • 1.er reintento: unos 1 minuto después del fallo.
  • 2.º reintento: unos 2 minutos más tarde.
  • 3.er reintento: unos 4 minutos más tarde, luego 8, 16, 32 y así sucesivamente.
  • Límite: los reintentos se espacian como máximo 6 horas.

Esto continúa hasta que tu endpoint acepta el evento, o hasta que el webhook lleva fallando más de 72 horas seguidas. En ese momento, Nowistay pone el estado del webhook en disabled y deja de enviarle nuevos eventos. Los contadores de fallos se reinician cada vez que una entrega tiene éxito, así que un incidente aislado nunca activa la desactivación automática.

Los registros de entrega se conservan durante 30 días y después se eliminan.

Reactiva un webhook desactivado

Un webhook desactivado se puede volver a activar desde la API cuando tu endpoint vuelva a funcionar correctamente. Envía un PATCH con status: "active":

curl -X PATCH https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example" \
  -H "Content-Type: application/json" \
  -d '{"status": "active"}'

Nowistay no reenvía los eventos que se produjeron mientras el webhook estaba desactivado. Si te has perdido alguno, haz una recuperación puntual con GET /public/v1/bookings usando un filtro de fechas y reanuda después tu flujo basado en webhooks.

Pausar, reanudar, eliminar

Puedes pausar temporalmente las entregas (durante una ventana de mantenimiento, por ejemplo) sin perder la configuración del webhook:

# Pause
curl -X PATCH https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}'

# Resume
curl -X PATCH https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example" \
  -H "Content-Type: application/json" \
  -d '{"status": "active"}'

# Delete permanently
curl -X DELETE https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example"

También puedes modificar la url, la lista de events o la description en la misma llamada. Cambiar la URL no rota el secreto de firma.

Inspecciona tus webhooks

# List all webhooks (optionally filtered by property)
curl "https://api.nowistay.com/public/v1/webhooks?propertyId=101" \
  -H "Authorization: Bearer nis_access_token_example"

# Read one webhook
curl https://api.nowistay.com/public/v1/webhooks/801 \
  -H "Authorization: Bearer nis_access_token_example"

La respuesta del listado muestra los mismos campos que la respuesta de creación, salvo el secreto de firma (solo se devuelve en la creación).

Reglas y límites de la URL

  • URL HTTP o HTTPS absoluta, de 2048 caracteres como máximo. HTTPS obligatorio en producción.
  • Sin credenciales de autenticación básica en la URL (https://user:pass@... se rechaza).
  • Sin localhost ni rangos de IP privados o reservados. Nowistay también resuelve el nombre de host en el momento de la entrega y bloquea las llamadas que resuelven a direcciones privadas.
  • Sin redirecciones HTTP. Nowistay no las sigue: la entrega se marca como fallida si tu servidor redirige.
  • Máximo 3 webhooks por propiedad, entre todas tus aplicaciones.
  • Descripción: opcional, 120 caracteres como máximo.

Buenas prácticas

  • Verifica la firma siempre. Es lo único que demuestra que la llamada procede realmente de Nowistay.
  • Sé idempotente. El mismo identificador de evento puede reenviarse tras un fallo. Guarda el id del cuerpo y omite el evento si ya lo has procesado.
  • Responde rápido. Confirma la llamada con un 2xx de inmediato y procesa el evento en un trabajo en segundo plano si es pesado.
  • Registra las cabeceras. Conserva Nowistay-Webhook-Event-Id y el identificador de la petición en tus registros. Agiliza el soporte.
  • Trata el webhook como un aviso, no como la fuente de verdad. Si tu lógica de negocio necesita campos sensibles o los datos más recientes, llama a GET /public/v1/bookings/{id} después de recibir el evento.

Siguientes pasos

¿Listo para poner tu alojamiento en piloto automático?

Únete a más de 1.000 gestores de alojamientos que ahorran horas cada semana con la comunicación con huéspedes impulsada por IA.