Skip to content
yaReserva

Search the site

Go to

Searches the 78 features, the 36 guides, the documentation and the frequently asked questions.

Book

Integration

Restaurant POS

For the point of sale that closes a tab “to the room”: the charge lands on the stay’s folio and the guest pays it on check-out.

What this solves

The POS keeps taking every other payment as usual: cash, card, transfer. Only one thing changes. When the waiter closes a tab “to the room”, the POS sends us the charge, it lands on the guest’s stay folio, and it is settled at check-out.

It works for Soft Restaurant, Parrot, Loyverse or any other point of sale that can make a signed HTTP call when closing a tab.

  • There are three calls: find the guest in a room, post the charge, and void it if the waiter got it wrong.
  • Amounts travel in cents and in the booking’s currency.
  • The ticket number is the idempotency key. Retrying with the same ticket number does not charge twice.

Credentials

The hotel creates the connection in its panel, under Settings → Restaurant POS, and receives the credential pair once and only once.

ValueExampleUse
keyIdyrpos_3f9a1c0b7d2e4a6f8b0c1d2ePublic. Identifies the connection.
secretyrpos_sk_9c1f… (64 hex)Secret. Signs the requests and never travels inside them.

Each connection belongs to one property: it only sees and charges stays in that property. The hotel can cap the maximum amount per charge, switch charging off, rotate the secret — the previous one stops working immediately — or revoke the connection outright.

Rule: The secret is shown only once

The panel shows it when it is created and when it is rotated, and never again. Store it right then wherever you keep your secrets; if it is lost, it has to be rotated.

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

How to sign

The three headers on every request
X-YR-POS-Key:       <keyId>
X-YR-POS-Timestamp: <segundos Unix, UTC>
X-YR-POS-Signature: sha256=<hex(HMAC-SHA256(secret, timestamp + "." + cuerpo))>
  • The body is the exact bytes you send. Sign after serializing the JSON and do not reformat it.
  • On GET requests the body is empty: you sign “timestamp + "."”, so the string ends at the dot.
  • The POS clock can differ from ours by at most five minutes. Outside that window the request is rejected; sync the clock with NTP.
  • Any failure — unknown or revoked key, wrong signature, time out of range — answers 401 INVALID_SIGNATURE and nothing is processed.
  • The body cannot exceed 64 KB, and there is a cap of 300 requests per minute per 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()

Find the guest

GET/rooms/{roomNumber}/guests

Who is staying in a room, so the waiter can confirm with the guest before posting the charge.

Path parameters

NameTypeRequiredDescription
roomNumberstringYesThe room number as the waiter types it.

Response

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

Errors

HTTPCodeWhenWhat to do
404ROOM_NOT_IN_HOUSENobody is staying in that room.Show it on the waiter’s screen and have them take another form of payment.

Note: It returns the minimum, on purpose

Only the first initial and the first surname, and the reservation code down to its last four characters. Enough for the waiter to confirm out loud without the restaurant screen showing a guest’s details to whoever walks past.

Charge to the room

POST/charges

Posts the consumption to the stay’s folio. On the folio it reads “POS <connection>: <description>”.

Request body

NameTypeRequiredDescription
externalIdstringYes1–100 characters: letters, digits and . _ : -. It is the ticket number and must be unique per connection.
roomNumberstringYesRoom number. It can be omitted if you send stayId or reservationCode.
stayIdUUIDNoThe one from the search. Use it when there is more than one stay in the room.
reservationCodestringNoFull reservation code, if the guest gives it.
amountCentsintegerYesGreater than zero and within the connection’s cap, if the hotel set one.
currencystringNoIf present, it must match the booking’s currency.
descriptionstringYes1–120 characters. This is what the guest reads on their folio.
linesarrayNoIf present, the sum of qty × unitCents must equal amountCents exactly. Discounts go in with a negative unitCents and the tip as one more line.

Request

json code
{
  "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 }
  ]
}

Response

201 · New charge
{
  "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
  }
}

Errors

HTTPCodeWhenWhat to do
401INVALID_SIGNATUREInvalid signature, time or key.Check the clock before anything else.
404ROOM_NOT_IN_HOUSENobody in house matching those details.Confirm the room with the guest and search again.
409CHARGES_NOT_ALLOWEDThe stay no longer accepts charges — checked out or not yet arrived — or the hotel switched charging off for this connection.Do not retry. Have the restaurant take another form of payment.
409MULTIPLE_STAYS_IN_ROOMThere are two stays in the same room.Retry sending stayId, the one from the search.
409EXTERNAL_ID_REUSEDThat ticket number was already used with a different amount or room.Use a new ticket number. The same number must mean the same charge.
413PAYLOAD_TOO_LARGEBody over 64 KB.Send fewer detail lines.
422VALIDATION_ERRORA field is missing or the lines do not add up to the total.“details.field” says which one.

Void a charge

POST/charges/{externalId}/void

Creates a credit note on the folio. The original charge is not deleted: it stays, with its note beside it.

Request body

NameTypeRequiredDescription
reasonstringYesWhy it is being voided. Visible on the folio and in the hotel’s panel.

Request

json code
{ "reason": "Se cargó a la mesa equivocada" }

Response

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

Errors

HTTPCodeWhenWhat to do
404CHARGE_NOT_FOUNDThere is no charge with that ticket number on this connection.Check the ticket number: you only see charges from your own connection.
409CHARGE_ALREADY_VOIDEDIt was already voided, by the POS or by the front desk.Treat it as success: the charge is no longer on the folio.
409CHARGES_NOT_ALLOWEDThe guest’s account is already closed.The front desk makes the adjustment from the panel.
422VALIDATION_ERROR“reason” is missing.Send the reason.

Idempotency and retries

  • Retry with the same externalId on any network error, timeout or 5xx. If the charge had already landed, you get 200 with “duplicate”: true and the same chargeId: it is not charged twice.
  • The charge record and the folio movement are written in the same transaction. Two simultaneous retries cannot duplicate the charge.
  • Generate a fresh timestamp and signature on every retry: the old signature expires after five minutes.
  • Do not retry a 4xx without fixing the request. The exception is 429, which you retry respecting Retry-After.
  • Voiding is safe to repeat: the second time it answers 409 CHARGE_ALREADY_VOIDED, which for you means the same as success.

Careful: Ticket numbers are not reused

If your POS restarts numbering each shift or each day, combine the ticket number with the date or the till before sending it. Two different consumptions sharing an externalId make the second look like a retry of the first, and it never reaches the guest’s folio.

Integrating a hotel?

Credentials come out of the hotel’s panel, and sandbox ones are created the same day. If the hotel does not have an account yet, start by registering it.