Integración con PMS
de servidor a servidor
Un endpoint de lectura para cargar el estado del socio en la reserva. Cinco webhooks firmados con HMAC para mantenerte al día con los cambios de saldo. Sin consultas periódicas.
GET /reservation-snapshot
Devuelve en una sola llamada todo lo necesario para mostrar la interfaz de crédito monetario durante la estancia o a la salida.
GET /api/v1/loyalty/reservation-snapshot?reservation_id=${reservationId}
Authorization: Bearer ${GUESTMAKER_API_KEY}
Accept: application/json
// Response (200)
{
"data": {
"member": {
"id": "uuid",
"member_number": "ABC123",
"email": "guest@example.com",
"tier": "silver",
"points_balance": 12500,
"spendable_points": 9500 // = points_balance - active holds
},
"active_holds": [
{
"hold_id": "uuid",
"external_reference_id": "BOOK-2026-001",
"points": 3000,
"amount_value": 30.00,
"currency": "EUR",
"surface": "booking_direct",
"status": "active",
"expires_at": "2026-06-01T12:30:00Z"
}
],
"config": {
"enabled": true,
"surfaces": { "booking_direct": true, "in_stay": true, "checkout": false },
"allowed_channels": ["direct", "website", "phone"],
"redemption_rate_eur": 100, // points per EUR 1
"min_points_per_redemption": 500,
"hold_minutes": 10
}
}
}Usa spendable_points para decidir qué puede aplicar el socio ahora. No uses points_balance. Las retenciones activas ya están descontadas.
Eventos de webhook
Cinco eventos. Todos firmados con HMAC. Se reintentan con espera exponencial (1m, 5m, 30m, 4h, 24h).
loyalty.credit.held{
"event": "loyalty.credit.held",
"tenant_id": "uuid",
"occurred_at": "2026-06-01T10:00:00Z",
"data": {
"hold_id": "uuid",
"member_id": "uuid",
"points": 3000,
"amount_value": 30.00,
"currency": "EUR",
"surface": "booking_direct",
"external_reference_id": "BOOK-2026-001",
"expires_at": "2026-06-01T10:10:00Z"
}
}loyalty.credit.confirmed{
"event": "loyalty.credit.confirmed",
"tenant_id": "uuid",
"occurred_at": "2026-06-01T10:05:00Z",
"data": {
"hold_id": "uuid",
"transaction_id": "uuid",
"member_id": "uuid",
"points": 3000,
"amount_value": 30.00,
"currency": "EUR",
"external_reference_id": "BOOK-2026-001"
}
}loyalty.credit.released{
"event": "loyalty.credit.released",
"tenant_id": "uuid",
"occurred_at": "2026-06-01T10:11:00Z",
"data": {
"hold_id": "uuid",
"external_reference_id": "BOOK-2026-001",
"cause": "expired" // "released" if called via /credit/release
}
}loyalty.credit.reversed{
"event": "loyalty.credit.reversed",
"tenant_id": "uuid",
"occurred_at": "2026-06-02T09:00:00Z",
"data": {
"hold_id": "uuid",
"refund_transaction_id": "uuid",
"member_id": "uuid",
"points": 3000,
"external_reference_id": "BOOK-2026-001",
"cause": "cancellation"
}
}loyalty.balance.changed{
"event": "loyalty.balance.changed",
"tenant_id": "uuid",
"occurred_at": "2026-06-01T10:05:00Z",
"data": {
"member_id": "uuid",
"new_balance": 9500
}
}Verificación de firma HMAC
Verifica
X-Loyalty-Signature en todos los webhooks entrantes. Rechaza payloads sin firma o con firma incorrecta.// Node.js: verify the X-Loyalty-Signature header on every webhook.
import crypto from 'node:crypto';
function verifyLoyaltyWebhook(rawBody: string, signature: string, secret: string) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
// Constant-time compare to avoid timing side channels.
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signature, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express handler
app.post('/webhooks/loyalty', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.header('X-Loyalty-Signature') || '';
if (!verifyLoyaltyWebhook(req.body.toString('utf8'), signature, process.env.LOYALTY_WEBHOOK_SECRET!)) {
return res.status(401).end();
}
const payload = JSON.parse(req.body.toString('utf8'));
// ... dispatch on payload.event
res.status(200).end();
});El secreto de firma se genera al registrar el webhook en Ajustes → Fidelización → Crédito monetario. Para rotarlo, añade una URL de webhook nueva, verifica con ambos secretos en paralelo y elimina la antigua.