Skip to content

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.

EventFires when
booking.createdA booking exists — from the portal, the website or a partner key
booking.updatedStatus, dates or unit of an existing booking changed
booking.cancelledA 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.

{
"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.

Every request carries:

X-MyRentalCalendar-Signature: t=1786784362, v1=6c1f…9ab

v1 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.

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.

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.

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.

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.