Technical contract · version 1.0 · https://api.webreservations.it
WebReservations is the direct booking engine for the properties listed in your credentials. This API lets you download direct reservations — those taken on the property's own website and those recorded by its back office — following the same pattern you use for other channels: ask what is new, download it, confirm you have taken it.
Each credential sees only the properties whose owner has requested the connection: the
list is returned by /v1/ping.
An Italian version of this document is also available, identical in content.
Three operations, in the pattern you already know from other channels:
| Operation | Purpose |
|---|---|
| GET /v1/reservations | list of what is new or changed |
| GET /v1/reservations/{id} | the detail of a single reservation |
| POST /v1/reservations/{id}/confirm | acknowledgement: stop sending it to me |
The recommended cycle is one pass every few minutes:
GET /v1/reservations: it returns the reservations you have not
acknowledged yet, or that have changed since you did;POST /v1/reservations/{id}/confirm with your own identifier;A cancelled reservation does not disappear: it comes back in the list with
"status": "cancelled". That is your signal to put the room back on sale.
We issue you two values:
| key | public, identifies your credentials. Sent in clear. |
| secret | confidential. With the recommended method it never travels. |
All requests must use HTTPS. Plain HTTP is rejected.
| X-WR-Key | your key |
| X-WR-Timestamp | Unix time in seconds. Allowed skew: 5 minutes |
| X-WR-Nonce | random, 16 to 64 characters A-Z a-z 0-9 _ -, different on every request |
| X-WR-Signature | HMAC-SHA256, lowercase hexadecimal |
The signature is computed over six elements separated by a single newline
(\n). Missing parts stay as empty lines — they are not skipped:
METHOD e.g. GET
PATH e.g. /v1/reservations (no host, no query string)
QUERY e.g. pending=1&limit=50 (empty when absent)
TIMESTAMP same value as X-WR-Timestamp
NONCE same value as X-WR-Nonce
SHA256 hex sha256 of the body; for a GET, sha256 of the empty string
Complete example in PHP:
$key = 'wr0123456789abcdef012345';
$secret = 'the-secret-we-issued-to-you';
$method = 'GET';
$path = '/v1/reservations';
$query = 'pending=1';
$body = '';
$ts = (string)time();
$nonce = bin2hex(random_bytes(12));
$toSign = $method . "\n" . $path . "\n" . $query . "\n"
. $ts . "\n" . $nonce . "\n" . hash('sha256', $body);
$signature = hash_hmac('sha256', $toSign, $secret);
$headers = array(
'X-WR-Key: ' . $key,
'X-WR-Timestamp: ' . $ts,
'X-WR-Nonce: ' . $nonce,
'X-WR-Signature: ' . $signature,
);
Each nonce is valid once: an intercepted and replayed request gets
request_replayed.
If your connector cannot sign requests, we accept
Authorization: Basic base64(key:secret) — but only if we have
configured the IP addresses you call from on your credentials. Without that
list, Basic is rejected: the method that exposes the secret on every request is accepted
only together with a network control. Send us the addresses (a prefix such as
80.88.87. covers a whole class) and we will enable them.
Checks that the credentials work and shows which properties they can see.
{
"ok": true,
"data": {
"client": "RoomCloud",
"properties": [194, 207],
"serverTime": "2026-09-02T10:15:00+02:00"
}
}
| Parameter | Meaning |
|---|---|
| pending | 1 (default) only those not yet acknowledged or changed since. 0 all of them within the window. |
| from, to | filter on arrival date, format YYYY-MM-DD |
| after | id to continue from: pass back the next value |
| limit | page size, maximum 200 (default 100) |
{
"ok": true,
"count": 2,
"next": 18344, // null when there is nothing more
"data": [ { ... }, { ... } ]
}
We return reservations departing from 60 days ago onwards. If you need a longer history, tell us and we will widen the window.
A single reservation. 404 if it does not exist or does not belong to a
property in your credentials: the two cases are deliberately indistinguishable.
The id of any room line of a multi-room reservation returns the whole reservation, with
its own id (see below); the same applies to confirm, whose
response carries the id actually acknowledged.
Acknowledgement. JSON body with your own identifier:
{ "reference": "CRS-91138035" }
From then on the reservation leaves the pending=1 lists until something
changes in the data we sent you.
{
"id": 18342,
"propertyId": 194,
"status": "confirmed", // or "cancelled"
"source": "website", // "website" | "admin_manual" | "agency"
"createdAt": "2026-09-02 11:30:00",
"checkin": "2026-09-22",
"checkout": "2026-09-25",
"nights": 3,
"guest": {
"firstName": "Mario",
"lastName": "Rossi",
"email": "m.rossi@example.com",
"phone": "+39 081 1234567"
},
"rooms": [{
"roomId": 77,
"roomName": "Doppia Vista Mare",
"partnerRoomId": 21829, // the roomId you know it by
"quantity": 1,
"adults": 2,
"children": 1,
"rateName": "BB Rimborsabile",
"partnerRateId": 14266, // the rateId you know it by
"nightlyAverage": 116.67, // room average per night
"dayPrices": [ // what each night cost
{ "day": "2026-09-22", "price": 110.00, "rooms": 1, "total": 110.00 },
{ "day": "2026-09-23", "price": 130.00, "rooms": 1, "total": 130.00 },
{ "day": "2026-09-24", "price": 110.00, "rooms": 1, "total": 110.00 }
]
}],
"amounts": {
"currency": "EUR",
"total": 350.00, // stay + supplements
"balance": 0.00
},
"extras": "Culla (EUR 15,00); Parcheggio (EUR 10,00)",
"notes": "Arriving after 10pm",
"payment": {
"method": "stripe", // stripe | paypal | nexi | bonifico
"status": "paid", // pending | guaranteed | paid | released
"paid": 350.00,
"reference": "pi_3Qabc123" // identifier at the gateway
}
}
Cancelled reservations additionally carry cancelledAt and
cancellationReason.
source tells where the reservation comes from: website (booked
online by the guest on the property's booking engine, including quotes and packages accepted
online), admin_manual (entered by the property's staff: phone, e-mail, walk-in)
and agency (entered by the staff on behalf of a travel agency). All three hold
rooms in the same way and arrive in the same list, with the same fields.
A reservation with more than one room type (for example 1 Double + 1 Suite, online or
from the back office) is one object with one entry per room type in
rooms[], each with its own codes, guests and dayPrices.
amounts.total is the total of all rooms, payment.paid what has been
collected for the whole reservation, checkin/checkout span all rooms.
The id stays the same for the life of the reservation.
If the property removes one room, the reservation comes back in the list with that room
no longer in rooms[] (and the totals updated): treat it as a modification and
release that room. When every room is cancelled, status becomes
cancelled and rooms[] lists all of them.
nights
already reflects this count.
For every reservation we send three things: amounts.total, the stay total
including supplements; rooms[].nightlyAverage, the room
average per night as computed by the engine at booking time; and extras, the
supplements as recorded (free text with name and amount).
rooms[].dayPrices carries the breakdown: price is what
one room cost that night, rooms how many were booked,
total the product of the two. The sum of total is the stay
without supplements; amounts.total includes them.
dayPrices is an empty
array. Empty means we do not know, not it cost nothing: we do not
derive those numbers by dividing the total by the nights, that would be an invented
value wearing the clothes of a fact. For the same reason a night with no price set does
not appear.
partnerRoomId and partnerRateId are null when
that room or rate is not mapped: the reservation is still valid, we simply do not know
which of your codes it corresponds to.
{ "ok": false, "error": "auth_failed", "message": "Authentication failed." }
The error field is stable and safe to branch on in code;
message may change.
| HTTP | error | What to do |
|---|---|---|
| 400 | invalid_date, missing_reference | fix the request |
| 401 | auth_failed | key, secret or signature do not match |
| 401 | signature_missing | signature headers are missing |
| 401 | timestamp_out_of_window | synchronise your server clock |
| 401 | invalid_nonce | nonce out of format (16–64 characters) |
| 401 | request_replayed | nonce reused: use a new one on every request |
| 403 | https_required | call over HTTPS |
| 403 | ip_not_allowed | address not enabled: let us know |
| 403 | basic_not_allowed | use the signature, or ask us to enable your IPs |
| 403 | no_properties | credentials with no properties: let us know |
| 404 | not_found | unknown id, outside your properties, or wrong route |
| 405 | method_not_allowed | wrong HTTP method for that route |
| 429 | rate_limited | over 120 requests per minute: slow down |
| 429 | too_many_attempts | too many failed authentications: clears within 15 minutes |
| 503 | service_unavailable | our problem, retry in a few minutes |
With the credentials in hand, the first command to try:
curl -s https://api.webreservations.it/v1/ping \
-H "X-WR-Key: YOUR-KEY" \
-H "X-WR-Timestamp: $TS" \
-H "X-WR-Nonce: $NONCE" \
-H "X-WR-Signature: $SIGNATURE"
If it answers with the list of properties, the connection is in place.