> ## Documentation Index
> Fetch the complete documentation index at: https://developer.maatstays.wtf/llms.txt
> Use this file to discover all available pages before exploring further.

# Booking notifications lifecycle

> The seven booking events as one state machine: what fires when, who caused it, what never fires, and how to apply them in the order they happened.

Seven of the fifteen webhook events describe a booking. Each one is a **transition** on the
same state machine, and every payload carries the `status` the booking reached — so the
right way to read them is as moves on this diagram, not as seven unrelated messages.

```mermaid theme={null}
stateDiagram-v2
    direction LR
    [*] --> pending: booking.requested
    [*] --> confirmed: booking.confirmed
    pending --> confirmed: booking.confirmed
    pending --> rejected: booking.rejected
    pending --> cancelled: booking.cancelled
    pending --> pending: booking.modified
    confirmed --> checked_in: booking.checked_in
    confirmed --> cancelled: booking.cancelled
    confirmed --> confirmed: booking.modified
    checked_in --> checked_in: booking.modified
    checked_in --> cancelled: booking.cancelled
    checked_in --> completed: booking.checked_out
    rejected --> [*]
    cancelled --> [*]
    completed --> [*]
```

A booking enters the diagram the moment **the guest's payment succeeds** — never before.
Which entry it takes depends on the listing: a listing with **instant book** on goes straight
to `confirmed`; one without it goes to `pending`, and the host owes an answer.

**The minutes before that are a hold, and they have their own two events.** A guest who
reaches checkout takes the nights off sale while their payment completes:
[`calendar.held`](/api-reference/webhooks/calendar.held) says so, and
[`calendar.released`](/api-reference/webhooks/calendar.released) says how it ended —
`expired` if the payment never landed, or `converted` with the `booking_reference` of the
stay that enters this diagram. They are not booking events and they are not on this state
machine: a hold is not a booking, and most of them never become one. See
[the unpaid fifteen minutes](/guides/webhook-delivery#the-unpaid-fifteen-minutes).

## When each event fires

| Event | Fires when | Status after |
| :- | :- | :- |
| `booking.requested` | The guest paid for a listing **without instant book**. The money is held; the host has to accept or decline. | `pending` |
| `booking.confirmed` | Either the guest paid for a listing **with instant book** (the booking is born confirmed), or the host **accepted** a pending request — in the Maat host app, or through [`POST /v1/bookings/{booking_reference}/confirm`](/guides/bookings). | `confirmed` |
| `booking.rejected` | The host **declined** a pending request (host app, or `POST …/reject`). The guest is refunded in full. | `rejected` |
| `booking.cancelled` | From `pending`: the guest **withdrew** the request before the host answered (full refund, no policy). From `confirmed`: the guest cancelled under the listing's cancellation policy, or the host withdrew the stay. From `checked_in`: the same two paths. `cancellation.initiated_by` says which side — `guest` or `host` — and never who. | `cancelled` |
| `booking.checked_in` | The **guest confirmed arriving** in the Maat app. Only the guest can do this, and only on a `confirmed` stay. | `checked_in` |
| `booking.checked_out` | The **guest confirmed leaving** in the Maat app. Only the guest, and only after `checked_in`. | `completed` |
| `booking.modified` | A change to the dates or the party size was **applied**: a reschedule or an extension once the guest paid the difference, a shortening once the refund was issued, or a change the host approved. The payload carries the new stay and the booking's **current** status. | unchanged (`pending`, `confirmed` or `checked_in`) |

Two things about the last three rows are easy to get wrong:

* **Check-in and check-out are the guest's own confirmations, not a clock.** Nothing fires at
  the check-in date or the check-out date. A stay whose guest never taps confirm stays
  `confirmed` for its whole life and produces neither event; if you need "the stay has
  started" on a date, compute it from `stay.check_in`, and treat `booking.checked_in` as a
  bonus signal that the guest is actually there.
* **A modification is not a new booking.** `booking.modified` keeps the same
  `booking_reference` and does not change the status; read the new `stay` from the payload
  and replace what you hold. It reaches a `pending`, `confirmed` or `checked_in` booking —
  including a request the host has not answered yet, because a guest changing their own
  still-pending request needs no approval to change it. So `booking.modified` can arrive
  with `status: pending`, before any `booking.confirmed`, and that is not an out-of-order
  delivery.

## What never fires

* **A pending request does not expire.** There is no timer that turns `pending` into
  `rejected` or `cancelled`; a request ends only when the host answers or the guest withdraws.
  If a request is old, that is a host who has not answered — nothing more.
* **A host-invited stay produces no booking event.** A booking a host creates by inviting a
  guest reaches `confirmed` through a different path and fires none of the seven; it is
  visible through [`GET /v1/bookings`](/guides/bookings) like any other. This is a known gap,
  recorded in the changelog.
* **A session fires none of the seven.** A booking on an experience or a service
  (`inventory_kind` other than `unit`) hangs off no unit, so it has no door times and no
  owning property, and every payload on this page states both as required fields. Nothing
  listing-keyed is delivered for one — see
  [the event catalogue](/guides/webhook-delivery#the-event-catalogue) — so a session reaches you
  through [`GET /v1/bookings`](/guides/bookings) or not at all. Two facts sit behind that and
  would outlast it: a session has no arrival to confirm, so it is never `checked_in` or
  `completed`; and it is never modified either, because every reschedule and extension path
  refuses a session — a change of mind there is a cancellation and a new booking.
* **Nothing fires before payment**, and nothing fires for a review — `review.created` is its
  own event, documented on [Webhooks](/guides/webhooks).

## Two timelines

**Request to book.** The guest pays on Monday for a stay starting Friday.

1. Monday — `booking.requested`, `status: pending`.
2. Monday evening — the host accepts: `booking.confirmed`, `status: confirmed`. (Or declines:
   `booking.rejected`, and the story ends.)
3. Wednesday — the guest adds a night and pays the difference: `booking.modified`,
   `status: confirmed`, the payload's `stay.check_out` is Saturday now.
4. Friday — the guest taps "I've arrived": `booking.checked_in`.
5. Saturday — the guest taps "I've left": `booking.checked_out`, `status: completed`.

**Instant book.** The same stay on a listing with instant book on: step 1 is
`booking.confirmed` directly, and steps 3–5 are identical. If the guest cancels on Tuesday
under the listing's policy, you receive `booking.cancelled` with `initiated_by: guest` and
nothing afterwards.

## Applying events in the order they happened

Deliveries are **at least once**, retried on failure, and can be replayed by you or by us
([redrive](/guides/webhook-delivery#redriving-a-delivery)), so the same `event_id` can arrive twice and two events
can arrive out of order. Three rules keep your copy correct:

1. **Deduplicate on `event_id`.** A replay carries the same id as the original.
2. **Order by `occurred_at` if you order at all.** It is the time the thing happened, stamped
   in the same transaction as the fact, so a retry or a redrive does not move it — see
   [what it is](/guides/webhook-delivery#what-occurred_at-actually-is). It is a partial order:
   two events can share an instant.
3. **Treat each event as a transition on the diagram above.** If the `status` in a payload is
   not a legal move from the status you hold, you have received an older event late: keep
   what you have. When in doubt, `GET /v1/bookings/{booking_reference}` is the truth and
   costs one call.

## Where the rest lives

* Registering an endpoint and the signature scheme: [Webhooks](/guides/webhooks) and
  [Webhook signatures](/guides/webhook-signatures). Delivery timing, retries and the full
  event catalogue: [Webhook delivery](/guides/webhook-delivery).
* Reading a booking and answering a request: [Bookings and reviews](/guides/bookings).
* Why none of the seven carries the guest's identity: [The data boundary](/guides/data-boundary).
* Nothing on this page can be exercised in a sandbox yet — no payment reaches one, so no
  booking exists there to move: [Sandbox](/guides/sandbox#what-the-sandbox-still-cannot-prove).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.