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.
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.
booking.created y booking.updated.GET /public/v1/bookings/{id}, con fechas, canal, estado, importes, campos relevantes para las misiones y tus parámetros de reserva personalizados.webhooks.write. POST /public/v1/webhooks requiere además bookings.read, porque el payload es el detalle completo de una reserva.2xx.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.
Cada entrega es un HTTP POST a tu URL, con estas cabeceras:
Content-Type: application/jsonUser-Agent: Nowistay-Webhooks/1.0Nowistay-Webhook-Id: 801Nowistay-Webhook-Event-Id: evt_LkP2sxR9zV8QyW... (único por evento)Nowistay-Webhook-Event-Type: booking.updatedNowistay-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.
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).
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);
}
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.
El objeto data.booking refleja lo que devuelve GET /public/v1/bookings/{id} cuando se llama sin el scope bookings.sensitive.read. Es decir:
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"
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:
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.
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.
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.
# 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).
https://user:pass@... se rechaza).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.id del cuerpo y omite el evento si ya lo has procesado.2xx de inmediato y procesa el evento en un trabajo en segundo plano si es pesado.Nowistay-Webhook-Event-Id y el identificador de la petición en tus registros. Agiliza el soporte.GET /public/v1/bookings/{id} después de recibir el evento.Guías para anfitriones: lee el blog.
¿Listo para poner tu alojamiento en piloto automático?