Keeping Availability in Sync

Your app needs to know what's on a property's calendar and keep up as it changes. The short version:

  1. Use webhooks. Load the calendar once, then let OwnerRez tell you what changed.
  2. Decide with type and status. Never with the title.
  3. Wait for pending entries to settle. Don't schedule a cleaning or message a guest for something that isn't confirmed yet.
  4. Handle all three actions. Create, update, and delete, including an update that turns a block into a booking.
  5. Catch up after a gap. If you miss events, ask about everything that has changed since you were last in sync.

Getting changes

Load once, then listen

When a host connects your app:

  1. Pull their calendar with GET /v2/bookings, using property_ids and from/to for the properties and dates you care about. The list includes blocks, holds and canceled entries unless you filter by status.
  2. Subscribe to booking webhooks.
  3. From then on, apply each webhook to your copy.

Send only the parameters the API reference lists for an endpoint. An unrecognized parameter is ignored today. Send property_id instead of property_ids and the first load fails with "Either 'property_ids' or 'since_utc' is required", while a since_utc request returns every property. From January 5, 2027, an unrecognized parameter returns 400. To page, request next_page_url exactly as returned until there isn't one, and start again from the first page if you change a filter. Don't page with offset: it's deprecated on every list, and from January 5, 2027, offset above 0 returns 400.

Don't poll on a timer. Webhooks arrive within seconds; polling every few minutes still lags.

Subscribe one way, not two

You can subscribe in either of two places:

  • App settings (the Webhooks section of your OAuth app on the Developer/API settings page): one URL for every account that connects to your app.
  • Per connection (POST /v2/webhooksubscriptions): a URL per connected account, per action, optionally narrowed to a category.

Pick one. If both cover the same entity, you get every event twice.

Which webhooks you need

You're building Subscribe to
Anything that tracks stays (cleaning, tasks, guest apps, owner portals) booking: create, update, and delete. Booking webhooks cover every calendar entry: guest bookings, owner stays, blocks and holds.
Dynamic pricing, booking websites property with the availability, rates and rules categories. availability fires when a booking, block or hold changes a property's open dates, or a stay rule changes; rates when rates or seasons change; rules when check-in times, policies or guest limits change. Then read GET /v2/calendar for that property. The calendar is recalculated after the change, so it can lag the webhook by a short time.
Guest details guest, if you show or sync guest contact info
Quote flows quote, if you create quotes and follow them to a booking

What a webhook carries

Every webhook is a POST with a JSON body like this (the booking is trimmed here):

{
  "id": "3f6e2a8c-1b7d-4c55-9e0a-6d2f8b1c4a70",
  "user_id": 56789,
  "action": "entity_update",
  "entity_type": "booking",
  "entity_id": 12345,
  "categories": ["dates"],
  "entity": {
    "id": 12345,
    "type": "booking",
    "status": "active",
    "property_id": 678,
    "arrival": "2026-12-10",
    "departure": "2026-12-15",
    "check_in": "16:00",
    "check_out": "10:00",
    "updated_utc": "2026-10-09T18:22:41Z"
  }
}
  • id identifies this delivery. A retry of the same delivery has the same id. It is not the booking's id; that's entity_id.
  • entity is the whole booking, the same object GET /v2/bookings/{id} returns, as it was when the change happened. A retry resends that same copy, so a retried webhook can be hours old.
  • arrival and departure are dates in the property's time zone. check_in and check_out are times in the property's time zone, filled from the property's defaults when the booking has none.

A delete carries no entity:

{
  "id": "9b0c4d12-7e3a-4f1b-8a26-5c9d0e7f3b14",
  "user_id": 56789,
  "action": "entity_delete",
  "entity_type": "booking",
  "entity_id": 12346
}

Look entity_id up in your own copy. If you don't have it, ignore the delete. That's normal, for example, when a block was created and deleted before you heard about it.

Delivery rules

  • Answer first, work later. Return a 2xx within 4 seconds. Save the payload (a queue or a table), answer, then process it in the background. A handler that calls our API or another service before answering will miss the 4 seconds when a host's bulk edit sends hundreds of webhooks at once, and those failures can pause your webhooks (below).
  • Store our ids. Keep OwnerRez's ids on every record you build from our data: the booking id, its property_id, and the user_id of the account that connected your app. Every webhook names the account (user_id) and the record (entity_type, entity_id), so with those stored you can match any webhook, including a delete, without calling us.
  • Expect retries. A failed delivery is retried 10 more times over the course of about 4 hours. Skip any delivery whose id you've already processed.
  • Expect out-of-order delivery. A retry can arrive after a newer event for the same booking. Keep the copy with the later updated_utc, or fetch the booking by id when the order matters to you. A deleted booking doesn't come back, so after a delete, ignore later webhooks for that entity_id.
  • Expect your own changes back. A booking your app creates or updates comes back to you as a webhook. Apply it like any other; it can include changes the host made at the same time.
  • Expect merged changes. Several changes made close together can be delivered as a single update. A booking created and deleted before we send anything arrives only as the delete.
  • Ignore what you don't recognize. We add fields, categories, and type values over time. Ignore fields you don't use, and treat a type you don't know as closed dates with no stay: don't schedule work or message anyone for it.
  • Keep your error rate down. If you get 100 or more deliveries in about an hour and a half or more of them fail, OwnerRez pauses webhooks for your whole app, per-connection subscriptions included. It emails the account that owns your app, with a copy to the app's Additional Contact Email. They stay paused until you click Re-enable Webhooks on your app's page. Nothing from the paused period is sent later, including retries that were waiting. If webhooks stop arriving, check your app's page. See Failure Policy.

Catching up after a gap

If your endpoint was down or your webhooks were paused:

  1. If they were paused, re-enable them first, so nothing new slips past while you catch up.

  2. Pick your starting point:

    • Endpoint down: when the last webhook you processed arrived, minus 5 minutes.
    • Webhooks paused: 5 hours before the pause began, because retries that were waiting when it began were dropped too.
  3. Ask for everything changed since then:

    GET /v2/bookings?since_utc=<starting point>
    

    Don't add status, date or property filters. Filters apply to the booking as it is now, so a booking that was canceled or moved out of your filter would be missing instead of showing you the change. Filter on your side.

  4. Follow next_page_url exactly as returned until there isn't one. Apply each booking as you would an update webhook.

since_utc returns bookings created, updated or canceled since then, with two gaps. It does not return deletes; a deleted booking just isn't in the list. And it skips a booking when only its door codes, tags, custom fields or agreements changed. Both reach you as webhooks, and during a gap shorter than about 4 hours, as retries. After a longer gap or a pause, also re-list every property and date range you track (property_ids and from/to, no since_utc, no status, plus whichever of include_door_codes, include_tags, include_fields and include_agreements you use). Drop the ids you hold that aren't returned, and take door codes, tags, fields and agreements from the fresh list.

What's on the calendar

Every entry on the calendar comes back from /v2/bookings. The type field tells you what it is.

type What it is Where it comes from
booking A guest's stay Channels, the booking website, the host, your API calls, iCal imports set to bookings
owner The owner staying, or owner guests The host, the owner portal, the API
block Dates closed with no stay attached: maintenance, owner-portal blocks, iCal imports set to blocks, and channel holds (see Pending) The host, the owner portal, imports, channels
quote_hold Dates held for an open quote, including Airbnb requests to book Quotes with dates held; Airbnb request to book
linked_availability Dates closed because a linked property is booked OwnerRez, automatically

is_block is deprecated; it's true for the last three. Use type.

The title is for people. Don't use it to decide anything. A block titled "Hold for HM5ABC1234" is a channel hold, not an owner stay, and a host can title a block anything they like.

Status: active, pending, canceled

status Meaning Dates closed?
active Confirmed Yes
pending OwnerRez is holding the dates, but the stay isn't confirmed Yes
canceled No longer happening No

What makes something pending

  • A channel reservation being processed. Airbnb, Vrbo, Booking.com and Marriott send a reservation in steps. OwnerRez closes the dates with a hold, a pending block, while it waits. About 6 in 10 channel bookings start this way. Airbnb holds are titled "Hold for" plus the confirmation code. Nearly all holds become the booking within 10 minutes, typically within a minute or two.
  • A request to book. The guest asked, and the host hasn't accepted yet. Airbnb requests show as quote_hold.
  • A direct booking waiting on payment or approval.
  • A guest changing dates on Airbnb. OwnerRez holds the new dates with a temporary block until the change is processed.

pending_until_utc is a deadline, and what happens when it passes depends on the source. A pending guest booking is canceled within about an hour if the host's rules say to cancel, or stays pending if the host chose to be notified instead. Channel holds and quote holds ignore the deadline and resolve when the channel or quote does. Every status change arrives as an update; don't work out expiry from pending_until_utc yourself.

How pending resolves

What happens What you receive
The reservation is confirmed An update to the same id: status becomes active, type changes from block or quote_hold to booking, and a guest appears. It is not a create.
The request is declined or expires An update: status becomes canceled
A pending guest booking passes its deadline unpaid An update: status becomes canceled, unless the host chose to be notified instead
An Airbnb date change is processed An update to the booking with its new dates, and a delete for the temporary block, usually within 10 minutes
Airbnb sends the same reservation twice The first hold is deleted and the reservation continues on a new id. This is rare.

Any booking's date change or move to another property arrives as an update to the same id.

Canceled can come back

A canceled entry can return to active or pending. A quote hold comes back if the quote is reopened. An Airbnb hold released while Airbnb was slow to confirm comes back when the confirmation arrives. Treat status as current state, not history.

Deletes

Hosts can delete blocks, and OwnerRez deletes temporary blocks when they're no longer needed: Airbnb date-change blocks, linked_availability blocks when the linked booking moves or ends, and a quote hold when its quote is deleted or its hold is turned off. You get a delete with no cancel before it.

What to act on, by use case

You're building Act on Wait or ignore
Availability (calendars, channel tools, booking websites, search) Every entry that isn't canceled, pending included. All of them close dates.  canceled
Dynamic pricing Property webhooks, then GET /v2/calendar Individual bookings
Cleaning and tasks booking and owner with status active. block only if you offer hosts a setting for it (default off), and only when active. pending until it settles; quote_hold; linked_availability; canceled
Guest apps, guidebooks, WiFi, door codes booking with status active; owner too if owners use your app pending until it settles; blocks; quote_hold; linked_availability; canceled
Guest messaging and AI assistants booking with status active. A quote_hold is a request to book waiting for the host, if you help answer those. Pending block entries, including "Hold for" channel holds; linked_availability; canceled
Guest screening and damage protection booking with status active. A date change arrives as an update; a cancellation as an update with status canceled. pending until it settles, unless you screen requests to book (quote_hold); owner; blocks
Occupancy and noise monitoring booking and owner with status active: someone is staying Blocks; quote_hold; linked_availability; canceled
Owner portals and owner reporting booking and owner with status active; block too if your portal shows closed dates pending until it settles; quote_hold; linked_availability; canceled
Analytics Every entry, canceled included. Use type to separate stays from closed dates. Nothing

For cleaning apps in particular: schedule when an entry becomes active, not when it first appears. If you want to show a tentative cleaning for a pending stay, show it as tentative and don't notify anyone. Key each task on the booking id: when a hold becomes a booking, the update has the same id, so update the task instead of creating a second one. When the booking is canceled or deleted, remove the task; if a canceled booking returns to active, schedule it again.

Related