This document explains the non-obvious business logic hidden within the Partner API. Understanding these rules is critical to building a robust integration.
Booking Creation
Payment Responsibility Determines the Entire Flow
Your partner account’s paymentResponsibility setting governs what happens after you create a booking:
| Responsibility | Booking Created | Payment | Booking Status |
|---|
CLEANOS | Booking is created | CleanLife sends a payment link to the customer asynchronously | waiting payment or in progress if free/fully-couponed |
PARTNER | Booking is created | Partner collects payment externally | Always in progress immediately |
For PARTNER responsibility partners: Your booking will always start as in progress. CleanLife does not send any payment request. You must call POST /partners/bookings/:bookingId/confirm-payment after collecting payment.
For CLEANOS responsibility partners: The booking may start as waiting payment. Listen for the booking.payment_failed webhook event to know if the payment link processing failed.
External Reference Is Your Safety Net
The externalReference field is the most important tool for preventing duplicate bookings:
- It is unique per partner account.
- If you attempt to create a booking with a duplicate
externalReference, the API returns 409 DUPLICATE_EXTERNAL_REFERENCE.
- You should generate a unique
externalReference from your own order system before calling the API.
- In the event of a timeout or network error on your side, you can retry the creation request with the same
externalReference — if the first attempt succeeded, you will receive the conflict error; if it failed, the unique constraint was never recorded and you can safely retry.
If a booking creation fails with 422 BOOKING_CREATION_FAILED, the externalReference was not saved. You can reuse it in a retry.
Failed Reference Save
In the rare scenario where a booking is created but the partner reference cannot be saved:
- The system automatically marks the booking as
FAILED.
- The
externalReference is not saved.
- You can safely retry the create request with the same
externalReference.
If the issue persists, contact support with the requestId from the error response.
Booking Update
Editable States Only
A booking can only be updated when in in progress or waiting payment status. Attempting to update a success, canceled, or failed booking returns 422 BOOKING_NOT_EDITABLE.
Rescheduling Requires an Appointment
You can only reschedule (change date or timeslot) a booking that has an associated serviceAppointment. If appointmentId is null, the booking has not yet been assigned to a team, and rescheduling will return 422 BOOKING_APPOINTMENT_NOT_FOUND.
Wait for the booking.appointment_status_changed webhook event (which indicates an appointment has been assigned) before attempting to reschedule.
Timeslot Partial Updates
When updating only date without timeslot, the existing startAt/endAt are preserved. When updating only timeslot without date, the existing date is preserved. You do not need to re-specify fields you are not changing.
externalReference Upsert on Update
If the booking was created without an externalReference, you can add one via the update endpoint. If one already exists, it can be changed — subject to uniqueness.
Booking Cancellation
Duplicate Webhook Prevention
When you cancel a booking via the Partner API, a single booking.cancelled webhook is sent. You will not receive a duplicate event for the same cancellation.
If CleanLife operations cancels a booking on your behalf, you will still receive a booking.cancelled webhook. Your handler should treat repeated events for the same booking as idempotent.
Refund Behavior on Cancellation
| Payment Responsibility | Refund Behavior |
|---|
CLEANOS | Cancellation action is CANCEL_AND_REFUND. CleanLife initiates a refund automatically if applicable. |
PARTNER | Cancellation action is CANCEL_ONLY. No refund is initiated by CleanLife. Your system is responsible for refunding the customer. |
Payment Confirmation
Only PARTNER Responsibility Can Confirm
The confirm-payment endpoint is exclusively available to partners with paymentResponsibility = PARTNER. Attempting to call it with CLEANOS responsibility returns 422 PAYMENT_RESPONSIBILITY_MISMATCH.
One-Time Confirmation
Once payment is confirmed, it cannot be undone. Attempting to confirm again returns 422 PAYMENT_ALREADY_CONFIRMED. This is by design — treat it as a safe idempotency check.
Confirmation After Cancellation Is Blocked
You cannot confirm payment for a canceled or failed booking. Cancel the booking only after resolving any payment disputes with your customer.
Webhook Event Sources
Webhook events are triggered when:
- You call a Partner API endpoint (e.g. create, update, cancel, or confirm payment on a booking).
- A booking you own is affected by CleanLife operations (e.g. appointment status changes or ops-initiated cancellations).
Your handler should be idempotent — the same event type may arrive more than once in edge cases.
Timeslot Interpretation
All timeslot times are treated as Asia/Riyadh local time (UTC+3). Send times in HH:MM or HH:MM:SS format together with the service date.
If your systems operate in a different timezone, always convert to Riyadh local time before building booking requests.
Domain Validation
Domain validation is a two-layer system:
- Allowed domain whitelist — configured by the CleanLife team per partner. The hostname of all webhook subscription URLs must match a domain in this list.
- Request domain check — if your partner account has configured domains and a request includes an
Origin or Referer header, it is validated against the whitelist. Server-to-server requests (no Origin/Referer) bypass this check.
Immutable Fields
Once set, the following fields cannot be changed:
| Field | Notes |
|---|
partnerClientId | Your partner account ID; assigned at creation |
bookingId | Assigned at creation |
partnerPaymentConfirmedAt | Set once by confirm-payment; cannot be cleared |
payloadHash (webhook deliveries) | Computed at delivery creation |