Skip to content
العربية All documentation Sign in

Your PMS by webhook

If the bookings already live in a system you control — a property management system, a channel manager, an in-house backend — this is the fastest lane and the only one that is real-time in both directions. You POST a signed event; Suitiee bills it against the host's package or wallet, creates the cleaning and dispatches a cleaner.

What you need

Step 1 — Create the endpoint

Integrations → Webhook Endpoints → Create. You are shown two values, once:

Webhook endpoints, with the receive URL and the secret

Step 2 — Map your units

Register one row per host under Clients → Partner Clients, using your id for them, and map your unit identifiers to Suitiee units. An inbound unit_id has to resolve to a host and a unit, or the event is accepted and then fails with that reason on its row.

Step 3 — Sign the request

Every request carries an HMAC-SHA256 of the raw body, keyed with your signing secret:

X-Suitiee-Signature: sha256=<hex hmac_sha256(rawBody, secret)>
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
$signature = 'sha256=' . hash_hmac('sha256', $body, $webhookSecret);
const body = JSON.stringify(payload);
const signature = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');

Sign the exact bytes you transmit. Re-serialising after signing — a different key order, different whitespace, different unicode escaping — produces a signature that will not match.

The body must also carry a top-level ISO 8601 timestamp, and it must be within five minutes of our clock in either direction. That is what stops a captured request being replayed.

If you publish from fixed addresses, add them to the endpoint's IP allowlist; anything else is then refused.

Step 4 — Send the event

POST https://suitiee.com/webhooks/receive/{partnerSlug}/{webhookId}

{
  "event_id": "res_9f8c12",
  "event_type": "checkout",
  "timestamp": "2026-09-04T14:00:00Z",
  "unit_id": "APT-204",
  "checkout_datetime": "2026-09-04T11:00:00Z",
  "next_checkin_datetime": "2026-09-04T16:00:00Z",
  "guest_name": "Sara Ali",
  "notes": "Late checkout approved; extra towels."
}
Field Required What it does
event_type yes Your event's name. Mapped to a cleaning action by the endpoint's event mappings, or its default action
unit_id yes Your identifier for the unit. Must resolve to a host and a unit
checkout_datetime yes When the guest left. Anchors the cleaning window
timestamp yes When you sent it. Inside the replay window
event_id strongly advised Your idempotency key — see below
next_checkin_datetime no The next arrival, if known. The clean is scheduled to finish before it. Omit it and the window is five hours
cleaning_window_minutes no An explicit window length
guest_name, guest_email, guest_phone, guest_language no Recorded on the booking. See the note below
notes no Free text passed to the cleaner

Always send a stable event_id. Duplicates are collapsed on it, so a retry after a timeout creates one cleaning rather than two. Without one, an id is derived from the event type and the reservation id, and failing that from a fingerprint of the body.

Step 5 — Read the answer

200 {"status":"received"} means the event is stored and queued. It does not mean the cleaning exists yet — that happens asynchronously, and the outcome appears on the event's own row under Integrations → Webhook Events, and through GET /partner/webhooks/events.

Webhook events with their processing trace

Status Meaning
200 received Accepted and queued
200 duplicate This event_id has been seen; nothing was processed again
401 The signature is missing or wrong, or the timestamp is missing or stale
403 The endpoint is switched off, or the source address is not allowlisted
404 The slug or endpoint id is wrong, or the account is not active
429 Above 100 requests a minute. Back off and retry

Anything discovered after acceptance does not fail the HTTP call. An empty wallet, an unmapped unit — those are business outcomes, not transport errors. The call is answered received, and the event row says failed with the reason. Only signature, timestamp and endpoint problems come back as 4xx.

Guest details are optional and stay optional

Send them on the booking event, on the checkout event, or never. An event that omits them leaves what we already hold alone rather than blanking it; an event that includes them updates it, which is how a corrected address reaches us.

Guest email and phone are personal data under the PDPL. They are stored on the booking, shown to the operator and host who own it, kept off every list column and never written to a log line. Send them only if your own basis for sharing them allows it — everything else works exactly the same without them.

Callbacks back to you

Set a callback URL and callback secret under Settings, and Suitiee POSTs you a signed update when a cleaning is created, changes status or completes, and the same for maintenance visits.

X-Suitiee-Callback-Signature: sha256=<hmac_sha256(body, callbackSecret)>

Answer 2xx. A non-2xx or a timeout is retried three times — after 30 seconds, 2 minutes and 10 minutes — and then recorded as permanently failed.

When it goes wrong

What you see What it means What to do
401 Invalid webhook signature The bytes signed are not the bytes sent Sign the serialised body verbatim and transmit that exact string
401 …outside acceptable window Your clock has drifted, or the event was queued and sent late Send the timestamp at the moment you fire, in UTC; check NTP
200 duplicate for a real second event Two different events share an event_id, or you sent none and the fallback collided Send a distinct, stable event_id per event
Event row says failed — unit not linked unit_id matched no unit Map it under Partner Clients and reprocess the row
Event row says failed — not enough balance The host's wallet and packages could not cover the clean Top up, then reprocess the row
Accepted, and no callback arrives No callback URL is set, or yours answered non-2xx three times Check Settings; the retries are 30 s / 2 min / 10 min
Nothing is charged and no cleaner appears The account is still in test mode See Test mode

Last updated 2026-09-04

Was this page helpful?