Skip to main content
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. 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 says so, and 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.

When each event fires

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 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 — so a session reaches you through GET /v1/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.

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), 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. 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