Saltar al contenido
yaReserva

Buscar en el sitio

Ir a

Se busca en las 78 funcionalidades, las 36 guías, la documentación y las preguntas frecuentes.

Agendar

Integración

POS de restaurante

Para el punto de venta que cierra una cuenta «a la habitación»: el cargo entra al folio de la estancia y el huésped lo paga al salir.

Qué resuelve

El POS sigue cobrando todo lo demás como siempre: efectivo, tarjeta, transferencia. Solo cambia una cosa. Cuando el mesero cierra una cuenta «a la habitación», el POS nos manda el cargo, entra al folio de la estancia del huésped y se paga al hacer check-out.

Sirve para Soft Restaurant, Parrot, Loyverse o cualquier otro punto de venta que pueda hacer una llamada HTTP firmada al cerrar la cuenta.

  • Son tres llamadas: buscar al huésped de una habitación, cargar, y anular si el mesero se equivocó.
  • Los importes viajan en centavos y en la moneda de la reserva.
  • El folio del ticket es la llave de idempotencia. Reintentar con el mismo folio no cobra dos veces.

Credenciales

El hotel crea la conexión en su panel, en Configuración → POS del restaurante, y recibe una sola vez el par de credenciales.

DatoEjemploUso
keyIdyrpos_3f9a1c0b7d2e4a6f8b0c1d2ePúblico. Identifica la conexión.
secretyrpos_sk_9c1f… (64 hex)Secreto. Firma las peticiones y nunca viaja en ellas.

Cada conexión pertenece a una propiedad: solo ve y carga a las estancias de esa propiedad. El hotel puede limitar el importe máximo por cargo, apagar los cargos, rotar el secreto —el anterior deja de valer en el acto— o revocar la conexión entera.

Regla: El secreto se ve una sola vez

El panel lo enseña al crearlo y al rotarlo, y no lo vuelve a enseñar. Guárdalo en ese momento donde guardes tus secretos; si se pierde, hay que rotar.

URL base
https://api.yareserva.com/api/v1/webhooks/pos

Cómo firmar

Los tres headers de toda petición
X-YR-POS-Key:       <keyId>
X-YR-POS-Timestamp: <segundos Unix, UTC>
X-YR-POS-Signature: sha256=<hex(HMAC-SHA256(secret, timestamp + "." + cuerpo))>
  • El cuerpo son los bytes exactos que envías. Firma después de serializar el JSON y no lo reformatees.
  • En los GET el cuerpo es vacío: se firma «timestamp + "."», o sea la cadena termina en el punto.
  • La hora del POS puede diferir de la nuestra como mucho cinco minutos. Fuera de ese margen la petición se rechaza; sincroniza el reloj con NTP.
  • Cualquier fallo —llave desconocida o revocada, firma incorrecta, hora fuera de margen— responde 401 INVALID_SIGNATURE y no se procesa nada.
  • El cuerpo no puede pasar de 64 KB, y hay un tope de 300 peticiones por minuto por IP.
bash
KEY=yrpos_3f9a1c0b7d2e4a6f8b0c1d2e
SECRET=yrpos_sk_...
BODY='{"externalId":"TK-000123","roomNumber":"204","amountCents":45000,"description":"Cena mesa 7"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -X POST https://api.yareserva.com/api/v1/webhooks/pos/charges \
  -H "Content-Type: application/json" \
  -H "X-YR-POS-Key: $KEY" \
  -H "X-YR-POS-Timestamp: $TS" \
  -H "X-YR-POS-Signature: sha256=$SIG" \
  --data-binary "$BODY"
Node.js
const crypto = require('crypto');

const body = JSON.stringify(payload);
const ts = Math.floor(Date.now() / 1000).toString();
const sig = crypto.createHmac('sha256', secret).update(`${ts}.${body}`).digest('hex');

// headers: X-YR-POS-Key, X-YR-POS-Timestamp: ts, X-YR-POS-Signature: `sha256=${sig}`
Python
import hashlib, hmac, json, time

body = json.dumps(payload, separators=(",", ":")).encode()
ts = str(int(time.time()))
sig = hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()

Buscar al huésped

GET/rooms/{roomNumber}/guests

Quién está hospedado en una habitación, para que el mesero lo confirme con el huésped antes de cargar.

Parámetros de ruta

NombreTipoObligatorioDescripción
roomNumberstringSíEl número de habitación tal como lo teclea el mesero.

Respuesta

200 · Hay alguien hospedado
{
  "data": {
    "items": [
      {
        "stayId": "5d0c3f1e-2b7a-4c1d-9e8f-0a1b2c3d4e5f",
        "roomNumber": "204",
        "guest": "J. Pérez",
        "reservationCode": "•••••••2345",
        "checkOut": "2026-09-17"
      }
    ]
  }
}

Errores

HTTPCódigoCuándoQué hacer
404ROOM_NOT_IN_HOUSENadie está hospedado en esa habitación.Enséñalo en la pantalla del mesero y que cobre por otro medio.

Dato: Devuelve lo mínimo, a propósito

Solo la inicial del nombre y el primer apellido, y el código de reserva con los últimos cuatro caracteres. Alcanza para que el mesero confirme en voz alta sin que la pantalla del restaurante enseñe los datos de un huésped a quien pase por ahí.

Cargar a la habitación

POST/charges

Mete el consumo al folio de la estancia. En el folio aparece como «POS <conexión>: <descripción>».

Cuerpo de la petición

NombreTipoObligatorioDescripción
externalIdstringSí1–100 caracteres: letras, dígitos y . _ : -. Es el folio del ticket y tiene que ser único por conexión.
roomNumberstringSíNúmero de habitación. Se puede omitir si mandas stayId o reservationCode.
stayIdUUIDNoEl de la búsqueda. Úsalo si hay más de una estancia en la habitación.
reservationCodestringNoCódigo completo de la reserva, si el huésped lo da.
amountCentsintegerSíMayor que cero y dentro del tope de la conexión, si el hotel puso uno.
currencystringNoSi viene, tiene que ser la moneda de la reserva.
descriptionstringSí1–120 caracteres. Es lo que el huésped lee en su folio.
linesarrayNoSi vienen, la suma de qty × unitCents tiene que dar exactamente amountCents. Los descuentos van con unitCents negativo y la propina como una línea más.

Petición

Código en json
{
  "externalId": "TK-000123",
  "roomNumber": "204",
  "stayId": "5d0c3f1e-2b7a-4c1d-9e8f-0a1b2c3d4e5f",
  "amountCents": 45000,
  "currency": "MXN",
  "description": "Cena mesa 7",
  "lines": [
    { "name": "Filete", "qty": 2, "unitCents": 21000 },
    { "name": "Propina", "qty": 1, "unitCents": 3000 }
  ]
}

Respuesta

201 · Cargo nuevo
{
  "data": {
    "chargeId": "b7e4c1d0-3a2f-4e5b-8c7d-9e0f1a2b3c4d",
    "externalId": "TK-000123",
    "status": "posted",
    "duplicate": false,
    "amountCents": 45000,
    "currency": "MXN",
    "roomNumber": "204",
    "guest": "J. Pérez",
    "reservationCode": "•••••••2345",
    "receivedAt": "2026-09-14T21:03:11Z",
    "voidedAt": null
  }
}

Errores

HTTPCódigoCuándoQué hacer
401INVALID_SIGNATUREFirma, hora o llave inválidas.Revisa el reloj antes que nada.
404ROOM_NOT_IN_HOUSENadie hospedado con esos datos.Confirma la habitación con el huésped y vuelve a buscar.
409CHARGES_NOT_ALLOWEDLa estancia ya no admite cargos —salió o no ha llegado— o el hotel apagó los cargos de esta conexión.No reintentes. Que el restaurante cobre por otro medio.
409MULTIPLE_STAYS_IN_ROOMHay dos estancias en la misma habitación.Reintenta mandando stayId, el de la búsqueda.
409EXTERNAL_ID_REUSEDEse folio ya se usó con otro importe o con otra habitación.Usa un folio nuevo. El mismo folio tiene que ser el mismo cargo.
413PAYLOAD_TOO_LARGECuerpo de más de 64 KB.Manda menos líneas de detalle.
422VALIDATION_ERRORFalta un campo o las líneas no suman el total.«details.field» dice cuál.

Anular un cargo

POST/charges/{externalId}/void

Genera una nota de crédito en el folio. El cargo original no se borra: se queda con su nota al lado.

Cuerpo de la petición

NombreTipoObligatorioDescripción
reasonstringSíPor qué se anula. Se ve en el folio y en el panel del hotel.

Petición

Código en json
{ "reason": "Se cargó a la mesa equivocada" }

Respuesta

200 · Anulado
{
  "data": {
    "chargeId": "b7e4c1d0-3a2f-4e5b-8c7d-9e0f1a2b3c4d",
    "externalId": "TK-000123",
    "status": "voided",
    "amountCents": 45000,
    "currency": "MXN",
    "voidedAt": "2026-09-14T21:41:02Z"
  }
}

Errores

HTTPCódigoCuándoQué hacer
404CHARGE_NOT_FOUNDNo hay cargo con ese folio en esta conexión.Comprueba el folio: solo ves los cargos de tu propia conexión.
409CHARGE_ALREADY_VOIDEDYa estaba anulado, por el POS o por recepción.Trátalo como éxito: el cargo ya no está en el folio.
409CHARGES_NOT_ALLOWEDLa cuenta del huésped ya se cerró.El ajuste lo hace recepción desde el panel.
422VALIDATION_ERRORFalta «reason».Manda el motivo.

Idempotencia y reintentos

  • Reintenta con el mismo externalId ante cualquier error de red, timeout o 5xx. Si el cargo ya había entrado, recibes 200 con «duplicate»: true y el mismo chargeId: no se carga dos veces.
  • El registro del cargo y el movimiento del folio se escriben en la misma transacción. Dos reintentos a la vez no pueden duplicar el cargo.
  • Genera un timestamp y una firma nuevos en cada reintento: la firma vieja caduca a los cinco minutos.
  • No reintentes un 4xx sin corregir la petición. La excepción es el 429, que se reintenta respetando Retry-After.
  • Anular es seguro de repetir: la segunda vez responde 409 CHARGE_ALREADY_VOIDED, que para ti es lo mismo que haber salido bien.

Cuidado: El folio del ticket no se reusa

Si tu POS reinicia la numeración cada turno o cada día, junta el folio con la fecha o con la caja antes de mandarlo. Dos consumos distintos con el mismo externalId hacen que el segundo se tome por un reintento del primero y nunca llegue al folio del huésped.

¿Vas a integrar un hotel?

Las credenciales salen del panel del hotel, y las de sandbox se crean el mismo día. Si el hotel todavía no tiene cuenta, empieza por registrarlo.