Skip to main content
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:
ResponsibilityBooking CreatedPaymentBooking Status
CLEANOSBooking is createdCleanLife sends a payment link to the customer asynchronouslywaiting payment or in progress if free/fully-couponed
PARTNERBooking is createdPartner collects payment externallyAlways 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:
  1. The system automatically marks the booking as FAILED.
  2. The externalReference is not saved.
  3. 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 ResponsibilityRefund Behavior
CLEANOSCancellation action is CANCEL_AND_REFUND. CleanLife initiates a refund automatically if applicable.
PARTNERCancellation 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:
  1. Allowed domain whitelist — configured by the CleanLife team per partner. The hostname of all webhook subscription URLs must match a domain in this list.
  2. 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:
FieldNotes
partnerClientIdYour partner account ID; assigned at creation
bookingIdAssigned at creation
partnerPaymentConfirmedAtSet once by confirm-payment; cannot be cleared
payloadHash (webhook deliveries)Computed at delivery creation