Webhooks
Instead of polling GET /bookings, register an endpoint and we post a signed
message to it whenever a booking changes. The agency sets this up in its portal
under Partner API → Webhooks; there is no API for registering endpoints,
because the person who owns the receiving system is the one who should decide
where data is sent.
The events
Section titled “The events”| Event | Fires when |
|---|---|
booking.created | A booking exists — from the portal, the website or a partner key |
booking.updated | Status, dates or unit of an existing booking changed |
booking.cancelled | A booking was cancelled and its nights released |
Events fire for every booking of the agency, not only the ones your key created. That is the point: a channel integration wants to know when the office cancels something by hand.
The envelope
Section titled “The envelope”{ "id": "01a005…", "event": "booking.created", "occurredAt": "2026-08-15T11:24:31.980Z", "data": { "id": "01a005…", "unitSlug": "marina-loft-2br", "unitName": "Marina Loft · 2BR", "type": "guest", "status": "confirmed", "source": "partner", "sourceRef": "your-order-8842", "arrival": "2027-04-05", "departure": "2027-04-12", "nights": 7, "adults": 2, "children": 0, "totalAmount": 291646, "currency": "AED" }}data is a snapshot taken when the event happened, not when it was
delivered — a retry six hours later still describes the moment it describes. It
carries no guest personal data, regardless of your key’s scopes: the body
travels to a URL we do not control.
id is unique per event. Endpoints can be called more than once for the same
event after a network failure, so key your processing on it and ignore ids
you have already seen.
Verifying the signature
Section titled “Verifying the signature”Every request carries:
X-MyRentalCalendar-Signature: t=1786784362, v1=6c1f…9abv1 is HMAC-SHA-256 over the exact string <t>.<raw request body>, hex
encoded, using the signing secret shown once when the endpoint was registered.
Compute it over the raw body bytes, before any JSON parsing — a re-serialised
body will not match.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, header, rawBody) { const t = /t=(\d+)/.exec(header)?.[1]; const v1 = /v1=([0-9a-f]+)/.exec(header)?.[1]; if (!t || !v1) return false;
// reject anything older than five minutes — the timestamp is inside the // signature, so an attacker cannot backdate a replayed request if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const a = Buffer.from(expected); const b = Buffer.from(v1); return a.length === b.length && timingSafeEqual(a, b);}Compare in constant time, as above. A plain === on a signature leaks its
content through timing, slowly but genuinely.
Each endpoint has its own secret. A message signed for one endpoint will not verify against another’s — that is deliberate, so one compromised receiver cannot forge messages for the others.
Answering
Section titled “Answering”Return any 2xx as soon as you have stored the event. We wait at most 10
seconds; anything slower counts as a failure, so do the real work after
answering rather than before.
Failures are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. After the last attempt the delivery is marked dead and no longer retried. An endpoint that fails 20 times in a row is switched off, and the agency sees why in its portal — so a receiver that is down for a day does not silently lose events without anyone noticing.
Testing it
Section titled “Testing it”The portal has a send test event button next to each endpoint; it posts a
signed ping envelope and shows you the HTTP status that came back. Use it
before you rely on the endpoint, and again whenever you change its URL.
Where we will and will not send
Section titled “Where we will and will not send”Endpoints must be https and must live on the public internet. Addresses inside
private networks — loopback, RFC-1918, carrier-NAT, link-local — are refused,
and the check runs twice: once when you register the URL, and again against
the address your hostname actually resolves to immediately before every send.
So an ordinary-looking domain whose DNS points at 10.0.0.5 is accepted at
registration and then refused at delivery, with endpoint address is not publicly routable in the log. If you see that on an endpoint you believe is
public, check what your DNS returns — including its IPv6 record.
Transport failures are reported coarsely on purpose (endpoint not reachable,
no answer within 10s). Your endpoint’s own HTTP status is always passed
through unchanged, which is the part that tells you something about your
service.
What is not here yet
Section titled “What is not here yet”No booking.* event carries guest details, and there are no events for
invoices, payments or units. If you need one, say so — each event is a promise
to keep delivering it, so the list grows on demand rather than in advance.