Webhook Event Types
TL;DR
Repull delivers 32 event types across reservations, booking requests, inquiries, listings, calendar, accounts, AI, payments, and system. This page is the canonical reference. The same catalog is available programmatically at GET /v1/webhooks/event-types.
Webhooks are HTTP POST callbacks Repull sends to a URL you control whenever something interesting happens — a new reservation, a guest message, a connection breaking, a payment refund. Subscribe to the events you care about and Repull will push real-time updates instead of you polling the API.
This page is the canonical source of truth for the full event catalog with sample payloads. The same data is also exposed programmatically at GET /v1/webhooks/event-types so you can render selectors or validators dynamically.
Health-monitoring tip
account.disconnected so your integration knows the moment a customer's PMS or OTA token expires upstream. The OAuth flow can't be recovered server-side — your end user must reconnect. Pairing this with account.created is the load-bearing signal for partner integrations that depend on uninterrupted PMS access.Catalog
| Domain | Event | Description |
|---|---|---|
| Reservations | reservation.created | A new reservation arrived from any connected channel or direct booking. |
reservation.updated | Dates, guest count, status, or pricing changed on an existing reservation. Also sent when a booking enters, changes or leaves an Airbnb hold for the guest — payment (pendingReason: guest_payment) or identity verification (guest_verification) — even when status stays pending; previousAttributes.status carries the raw status before. | |
reservation.cancelled | A reservation was cancelled by the guest, host, or platform. | |
reservation.message.sent | A host-side message was sent on a reservation thread — in the channel's own app (e.g. the Airbnb app; source: "channel") or through Repull (source: "repull": the API, the dashboard, an automation or AI). Sent once per message, whichever copy Repull stores first. externalMessageId and messageId match GET /v1/conversations/{id}/messages. Currently Airbnb and Booking.com. | |
reservation.message.updated | A message on a reservation thread was edited on the channel (Airbnb reports the edit). body is the new text, previousBody the text before, editedAt when. Guest and host messages both; read receipts and reactions do not fire it. | |
reservation.message.received | A new inbound message arrived on a reservation thread. | |
reservation.alteration.created | A new reservation alteration (change of dates, guest count, or price) was requested — currently Airbnb only. Detected on the next alteration sync after the request lands upstream. | |
reservation.alteration.responded | A pending reservation alteration was accepted, declined, or cancelled. The status field on the object reflects the new state. | |
reservation.request.created | A guest asked to book and the reservation is waiting on the host (status pending). Answer it with POST /v1/reservations/{id}/accept or /decline before respondBy — Airbnb expires an unanswered request after 24 hours. Fires for requests made in any channel app, not only through the API. Currently Airbnb, plus pending bookings a connected PMS relays. | |
reservation.request.updated | A booking request stopped waiting on the host. requestStatus says how: accepted (it is now a booking — reservation.created fires too), declined by the host, expired (nobody answered in time), or voided (withdrawn by the guest, or voided by the channel — e.g. failed verification). sourceStatus keeps the channel’s own value. Fires when the channel reports it, whoever acted and wherever. | |
| Inquiries | inquiry.created | A guest asked about dates before booking. The object is the inquiry as GET /v1/inquiries returns it; answer it with POST /v1/conversations/{conversationId}/pre-approval or /special-offers. Currently Airbnb, plus inquiries a connected PMS relays (relayedBy). |
inquiry.updated | An inquiry moved: its status (open, pre_approved, special_offer_sent, booked, expired, declined, not_possible), its dates, its guest count, or the reservation it became. previousAttributes names exactly what changed. Fires whether the host acted through the API, a connected app or the Airbnb app. An inquiry whose dates simply pass is shown as expired by the GET but fires no event unless the channel reports it. | |
| Listings | listing.created | A new property was synced into Repull from a connected PMS or channel. |
listing.updated | Listing content, amenities, photos, or status changed. | |
listing.deleted | A property was removed from Repull or the upstream PMS. | |
listing.suspended | The channel suspended the listing. Writes are still accepted and silently applied to nothing until it is reinstated. | |
listing.reactivated | The channel lifted a suspension and the listing accepts writes again. | |
| Calendar | calendar.updated | Availability or pricing for a listing was updated. |
| Accounts | account.created | An OAuth or API credential connection was completed by an end user. |
connect.session.completed | A user finished a Connect session, on any channel or PMS. Carries the session id, your state token and the account that was connected, so you can tie the connection to your own user. | |
account.disconnected | A PMS or channel connection was revoked, expired, or rejected by the upstream provider. The reason field disambiguates: refresh_token_rejected, manual_disconnect, auth_expired, revoked_upstream. | |
| Reviews | review.created | A new review was received on a reservation — from the guest (about the host/property) or the host (about the guest). reviewerRole disambiguates. Detected on the next review sync; providers do not push reviews in real time, so latency is bounded by the sync interval. |
review.responded | A public host response to a review was recorded. Fires when the review first appears with a response, or when a response is added to a previously unanswered review. | |
| AI | ai.operation.completed | An async AI run (review response, message draft, pricing suggestion) finished. |
ai.operation.failed | An async AI run terminated with an error and will not be retried. | |
| Payments | payment.completed | A guest payment was successfully captured. |
payment.refunded | A previous payment was refunded in part or in full. | |
payout.completed | Airbnb paid the host. Sent once per payout, the first time Repull sees it, for payouts dated in the last 14 days. data.payout and data.lines are exactly what GET /v1/channels/airbnb/transactions?payout_id= returns for it, with the same transactionIds, so you can deduplicate across webhooks and reads. The lines' signed amounts sum to data.payout.payout.paidOutAmount. Payouts are found by the automatic refresh (about daily per account) or a POST /v1/channels/airbnb/transactions. | |
| Migrations | migration.completed | An import into a migration workspace finished. workspaceId (on the event) names the migration; data.results has what was processed per entity. Fires after the first import and after every re-run from POST /v1/migrations/{workspaceId}/import. |
migration.failed | An import into a migration workspace stopped with an error. The connection is kept — re-run it with POST /v1/migrations/{workspaceId}/import once the cause (usually the source PMS credentials) is fixed. | |
| System | repull.ping | A diagnostic delivery used by the dashboard to verify endpoint reachability. |
usage.quota.warning | Your account has used 80% of a request quota for the current window. Sent at most once per account per window — it is a heads-up, not a refusal. scope names which quota — today the only value is "daily_requests" — and topOperation names the operation driving the traffic so a runaway loop can be found before the cap stops it. |
Delivery Envelope
Every webhook delivery uses the same outer JSON envelope. Only the data field changes per event type. Dedupe on eventId — it stays stable across retries and replays. (The per-attempt id lives in the X-Repull-Delivery-Id header and changes on every retry, so it is not safe for dedupe.)
{
"event": "reservation.created",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": { /* per-event payload, see sections below */ }
}Stay terms on reservation events
Every reservation.created, reservation.updated and reservation.cancelled delivery carries the terms of the stay alongside the identifiers, so you can decide whether a refund is owed and when the unit frees up without a second API call.
| Field | What you get |
|---|---|
object.cancellationPolicy | The policy the booking was made under, verbatim from the channel. Airbnb, direct and owner bookings send a named code — flexible, moderate, firm_14, strict_14_with_grace_period, better_strict_with_grace_period, super_strict_30, super_strict_60, tiered_pricing_non_refundable, long_term_flexible. Booking.com sends its numeric policy id as a string ("1", "74", "121") — resolve it against the property's policy set on Booking.com. null when the channel sent none. |
object.checkInTime | Local check-in time, HH:MM on a 24-hour clock in the property's timezone — not UTC, and not yours. null when unknown. |
object.checkOutTime | Local check-out time, same format. Pair it with object.checkoutDate to schedule the turnover clean. null when unknown. |
These values are never defaulted. A booking with no recorded policy arrives as null, not as a plausible-looking guess — a wrong policy costs more than a missing one. All three are also readable on GET /v1/reservations if you need to backfill a delivery you missed: the times as checkInTime / checkOutTime, the policy as financials.cancellationPolicy.
What reservation events do not carry
The reservation snapshot is deliberately narrow: identifiers, dates, status and stay terms. Guest contact details, payment instruments and pricing are left off every delivery — they are the fields most likely to differ per channel, and shipping them on every callback would put them in your logs. Fetch them with GET /v1/reservations/{id} when you need them.
Separately, some fields are not available at all for Airbnb reservations — not on the webhook, and not on the API either, because Airbnb does not give them to any partner. If you need these, they have to come from your own system or from the guest.
| Field | Airbnb | Where to get it instead |
|---|---|---|
| Guest email | Not provided. Airbnb never releases a guest's real address; the most any partner can be granted is an anonymised @guest.airbnb.com relay, which Repull does not surface. A reservation that does show an address got it from somewhere else — a past direct booking by the same guest, or manual entry. | Message the guest on the reservation thread, or collect an address yourself at check-in. Booking.com and direct bookings do carry a real address on primaryGuest.email. |
| Payment method | Not provided. Airbnb collects from the guest itself and pays the host out; the instrument is never exposed. | Nothing to fetch — there is no card behind an Airbnb booking from the host's side. Booking.com virtual cards arrive on the reservation; direct bookings settle through your own processor. |
| Payment reference | No per-reservation reference. Airbnb settles by payout, which covers many reservations at once. | GET /v1/channels/airbnb/transactions for settlement-level detail, once payouts have been synced for the account. |
| Notes | Not provided. Airbnb has no free-text note field on a reservation. | Anything guest-stated arrives as a message on the reservation thread — see reservation.message.received. |
Reservations
reservation.created
A new reservation arrived from any connected channel or direct booking.
reservation.createdreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "900001",
"uid": "HMEXAMPLE1",
"channel": "airbnb",
"listingId": "5668",
"customerId": "1",
"checkinDate": "2026-06-10",
"checkoutDate": "2026-06-16",
"status": "confirmed",
"cancellationPolicy": "firm_14",
"checkInTime": "16:00",
"checkOutTime": "10:00",
"guestId": "900002",
"checkIn": "2026-06-10",
"checkOut": "2026-06-16",
"source": "airbnb",
"platform": "airbnb",
"confirmationCode": "HMEXAMPLE1",
"totalPrice": "1320.00",
"currency": "GBP",
"guestDetails": {
"numberOfGuests": 2,
"numberOfAdults": 2,
"numberOfChildren": 0,
"numberOfInfants": 0,
"numberOfPets": 0
},
"primaryGuest": {
"id": "900002",
"firstName": "Taylor",
"lastName": "Reed",
"phone": "15555550142",
"language": "en-US"
},
"occupancy": {
"adults": 2,
"children": 0,
"infants": 0,
"pets": 0,
"total": 2
},
"financials": {
"totalPrice": 1320,
"currency": "GBP",
"paymentStatus": "pending",
"cancellationPolicy": "firm_14",
"host": {
"accommodation": 1160,
"discounts": [],
"guestFees": [
{
"name": "Guest service fee",
"type": "guest_service",
"amount": 0,
"vat": 0
}
],
"hostFees": [
{
"name": "Host service fee",
"type": "host_service",
"amount": 39.6,
"vat": 0
}
],
"taxes": [],
"revenue": 1280.4
},
"guest": {
"totalPrice": 1320,
"fees": [
{
"name": "Cleaning Fee",
"type": "cleaning",
"amount": 160
}
],
"taxes": []
}
},
"createdAt": "2026-05-01T12:34:56.000Z",
"updatedAt": "2026-05-01T12:34:56.000Z",
"bookedAt": "2026-05-01T12:34:50.000Z",
"guestName": "Taylor Reed"
},
"revision": "2026-05-01T12:34:56.000Z"
}
}reservation.updated
Dates, guest count, status, or pricing changed on an existing reservation. Also sent when a booking enters, changes or leaves an Airbnb hold for the guest — payment (pendingReason: guest_payment) or identity verification (guest_verification) — even when status stays pending; previousAttributes.status carries the raw status before.
reservation.updatedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "900001",
"uid": "HMEXAMPLE1",
"channel": "airbnb",
"listingId": "5668",
"customerId": "1",
"checkinDate": "2026-06-10",
"checkoutDate": "2026-06-16",
"status": "confirmed",
"cancellationPolicy": "firm_14",
"checkInTime": "16:00",
"checkOutTime": "10:00"
},
"previousAttributes": {
"checkinDate": "2026-06-11",
"checkoutDate": "2026-06-16"
}
}
}reservation.cancelled
A reservation was cancelled by the guest, host, or platform.
reservation.cancelledreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "900001",
"uid": "HMEXAMPLE1",
"channel": "airbnb",
"listingId": "5668",
"customerId": "1",
"checkinDate": "2026-06-10",
"checkoutDate": "2026-06-16",
"status": "cancelled",
"cancellationPolicy": "firm_14",
"checkInTime": "16:00",
"checkOutTime": "10:00"
},
"cancelledAt": "2026-05-01T14:00:00.000Z",
"cancelledBy": "guest",
"reason": "guest_requested"
}
}reservation.message.sent
A host-side message was sent on a reservation thread — in the channel's own app (e.g. the Airbnb app; source: "channel") or through Repull (source: "repull": the API, the dashboard, an automation or AI). Sent once per message, whichever copy Repull stores first. externalMessageId and messageId match GET /v1/conversations/{id}/messages. Currently Airbnb and Booking.com.
reservation.message.sentreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"reservationId": "235970",
"threadId": "900301",
"from": {
"type": "host",
"name": "Casey"
},
"body": "The unit is free from the 18th, so an early check-in should work. I will confirm by noon.",
"sentAt": "2026-09-25T13:02:17.000Z",
"messageId": "1854462",
"externalMessageId": "32877308873",
"externalThreadId": "2648057088",
"confirmationCode": "HMJTFCEDFB",
"listingId": "23893",
"channel": "airbnb",
"source": "channel",
"direction": "outbound",
"senderType": "host",
"senderName": "Casey",
"senderAvatar": null,
"translatedBody": null,
"attachments": [],
"isAutomated": false,
"aiGenerated": false,
"status": null,
"revision": "2026-09-25T13:02:17.000Z"
}
}reservation.message.updated
A message on a reservation thread was edited on the channel (Airbnb reports the edit). body is the new text, previousBody the text before, editedAt when. Guest and host messages both; read receipts and reactions do not fire it.
reservation.message.updatedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"reservationId": "235970",
"threadId": "900301",
"from": {
"type": "host",
"name": "Casey"
},
"body": "The unit is free from the 18th, so an early check-in should work. I will confirm by noon.",
"previousBody": "The unit is free from the 18th, so an early check-in shoud work. I will confirm by noon.",
"editedAt": "2026-09-25T13:02:28.000Z",
"sentAt": "2026-09-25T13:02:17.000Z",
"messageId": "1854462",
"externalMessageId": "32877308873",
"externalThreadId": "2648057088",
"confirmationCode": "HMJTFCEDFB",
"listingId": "23893",
"channel": "airbnb",
"direction": "outbound",
"senderType": "host",
"senderName": "Casey",
"senderAvatar": null,
"translatedBody": null,
"attachments": [],
"isAutomated": false,
"aiGenerated": false,
"status": null,
"revision": "2026-09-25T13:02:28.000Z"
}
}reservation.message.received
A new inbound message arrived on a reservation thread.
reservation.message.receivedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"reservationId": "215906",
"threadId": "thr_01HX5XPQ2K",
"from": {
"type": "guest",
"name": "Alex Morgan"
},
"body": "Hi! What time can we check in?",
"sentAt": "2026-05-01T15:00:00.000Z",
"messageId": "1847801",
"externalMessageId": "32837172376",
"externalThreadId": "2603530383",
"confirmationCode": "HMEXAMPLE1",
"listingId": "5668",
"channel": "airbnb",
"direction": "inbound",
"senderType": "guest",
"senderName": "Alex Morgan",
"senderAvatar": null,
"translatedBody": null,
"attachments": [
{
"id": "88412",
"url": "https://files.example.com/message-attachments/1847801-1714575600000-k3j9x2m1q.jpg",
"imageUrl": "https://files.example.com/message-attachments/1847801-1714575600000-k3j9x2m1q.jpg",
"type": "image",
"contentType": "image/jpeg",
"createdAt": "2026-05-01T15:00:02.000Z"
}
],
"isAutomated": false,
"aiGenerated": false,
"status": "received",
"revision": "2026-05-01T15:00:00.000Z"
}
}reservation.alteration.created
A new reservation alteration (change of dates, guest count, or price) was requested — currently Airbnb only. Detected on the next alteration sync after the request lands upstream.
reservation.alteration.createdreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "4471",
"alterationId": "RAX9K2M4C1",
"channel": "airbnb",
"reservationId": "215906",
"customerId": "1",
"status": "pending",
"initiator": "guest",
"confirmationCode": "HMEXAMPLE1",
"platform": "airbnb",
"reason": null,
"notes": "Flight moved, can we stay two more nights?",
"currency": "GBP",
"original": {
"checkIn": "2026-06-10",
"checkOut": "2026-06-16",
"guestCount": 2,
"totalPrice": "1320.00",
"listingId": "5668"
},
"requested": {
"checkIn": "2026-06-10",
"checkOut": "2026-06-18",
"guestCount": 2,
"totalPrice": "1760.00",
"listingId": "5668",
"airbnbListingId": null
},
"createdAt": "2026-05-01T13:00:00.000Z",
"updatedAt": "2026-05-01T13:00:00.000Z",
"respondedAt": null
},
"changes": {
"checkOut": {
"from": "2026-06-16",
"to": "2026-06-18"
},
"totalPrice": {
"from": "1320.00",
"to": "1760.00"
}
},
"revision": "2026-05-01T13:00:00.000Z"
}
}reservation.alteration.responded
A pending reservation alteration was accepted, declined, or cancelled. The status field on the object reflects the new state.
reservation.alteration.respondedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "4471",
"alterationId": "RAX9K2M4C1",
"channel": "airbnb",
"reservationId": "215906",
"customerId": "1",
"status": "accepted",
"initiator": "guest"
},
"respondedAt": "2026-05-01T16:30:00.000Z"
}
}reservation.request.created
A guest asked to book and the reservation is waiting on the host (status pending). Answer it with POST /v1/reservations/{id}/accept or /decline before respondBy — Airbnb expires an unanswered request after 24 hours. Fires for requests made in any channel app, not only through the API. Currently Airbnb, plus pending bookings a connected PMS relays.
reservation.request.createdreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "236354",
"uid": "HMT3X9KQZ2",
"channel": "airbnb",
"listingId": "23892",
"customerId": "1",
"checkinDate": "2026-10-02",
"checkoutDate": "2026-10-06",
"status": "pending",
"cancellationPolicy": "firm_14",
"checkInTime": "16:00",
"checkOutTime": "10:00",
"guestId": "231877",
"checkIn": "2026-10-02",
"checkOut": "2026-10-06",
"source": "airbnb",
"platform": "airbnb",
"confirmationCode": "HMT3X9KQZ2",
"totalPrice": "812.00",
"currency": "USD",
"guestDetails": {
"numberOfGuests": 2,
"numberOfAdults": 2,
"numberOfChildren": 0,
"numberOfInfants": 0,
"numberOfPets": 0
},
"primaryGuest": {
"id": "231877",
"firstName": "Jordan",
"lastName": "Ellis",
"language": "en-US"
},
"occupancy": {
"adults": 2,
"children": 0,
"infants": 0,
"pets": 0,
"total": 2
},
"financials": {
"totalPrice": 812,
"currency": "USD",
"cancellationPolicy": "firm_14",
"host": {
"accommodation": 700,
"discounts": [],
"guestFees": [],
"hostFees": [
{
"name": "Host service fee",
"type": "host_service",
"amount": 24.36,
"vat": 0
}
],
"taxes": [],
"revenue": 787.64
},
"guest": {
"totalPrice": 812,
"fees": [
{
"name": "Cleaning Fee",
"type": "cleaning",
"amount": 112
}
],
"taxes": []
}
},
"createdAt": "2026-09-22T09:00:00.000Z",
"updatedAt": "2026-09-22T09:00:05.000Z",
"bookedAt": "2026-09-22T09:00:00.000Z",
"guestName": "Jordan Ellis",
"respondBy": "2026-09-23T09:00:00.000Z"
},
"requestStatus": "pending",
"respondBy": "2026-09-23T09:00:00.000Z",
"occurredAt": "2026-09-22T09:00:05.000Z",
"revision": "2026-09-22T09:00:05.000Z"
}
}reservation.request.updated
A booking request stopped waiting on the host. requestStatus says how: accepted (it is now a booking — reservation.created fires too), declined by the host, expired (nobody answered in time), or voided (withdrawn by the guest, or voided by the channel — e.g. failed verification). sourceStatus keeps the channel’s own value. Fires when the channel reports it, whoever acted and wherever.
reservation.request.updatedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "236354",
"uid": "HMT3X9KQZ2",
"channel": "airbnb",
"listingId": "23892",
"customerId": "1",
"checkinDate": "2026-10-02",
"checkoutDate": "2026-10-06",
"status": "confirmed",
"cancellationPolicy": "firm_14",
"checkInTime": "16:00",
"checkOutTime": "10:00",
"checkIn": "2026-10-02",
"checkOut": "2026-10-06",
"platform": "airbnb",
"confirmationCode": "HMT3X9KQZ2",
"primaryGuest": {
"id": "231877",
"firstName": "Jordan",
"lastName": "Ellis",
"language": "en-US"
},
"createdAt": "2026-09-22T09:00:00.000Z",
"updatedAt": "2026-09-22T11:42:10.000Z",
"bookedAt": "2026-09-22T09:00:00.000Z"
},
"requestStatus": "accepted",
"previousAttributes": {
"status": "pending"
},
"sourceStatus": "accept",
"occurredAt": "2026-09-22T11:42:10.000Z",
"revision": "2026-09-22T11:42:10.000Z"
}
}Inquiries
inquiry.created
A guest asked about dates before booking. The object is the inquiry as GET /v1/inquiries returns it; answer it with POST /v1/conversations/{conversationId}/pre-approval or /special-offers. Currently Airbnb, plus inquiries a connected PMS relays (relayedBy).
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "25173",
"conversationId": "164743",
"listingId": "23892",
"customerId": "1",
"channel": "airbnb",
"status": "open",
"checkIn": "2026-10-23",
"checkOut": "2026-11-11",
"guests": {
"total": 2,
"adults": 2,
"children": 0,
"infants": 0,
"pets": 0
},
"expectedPayout": {
"amount": 2152.6,
"currency": "USD"
},
"reservationId": null,
"relayedBy": null,
"respondBy": "2026-09-23T17:40:38.725Z",
"respondedAt": null,
"createdAt": "2026-09-22T17:40:39.000Z",
"updatedAt": "2026-09-22T17:40:39.000Z"
},
"occurredAt": "2026-09-22T17:40:39.000Z",
"revision": "2026-09-22T17:40:39.000Z"
}
}inquiry.updated
An inquiry moved: its status (open, pre_approved, special_offer_sent, booked, expired, declined, not_possible), its dates, its guest count, or the reservation it became. previousAttributes names exactly what changed. Fires whether the host acted through the API, a connected app or the Airbnb app. An inquiry whose dates simply pass is shown as expired by the GET but fires no event unless the channel reports it.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "25173",
"conversationId": "164743",
"listingId": "23892",
"customerId": "1",
"channel": "airbnb",
"status": "pre_approved",
"checkIn": "2026-10-23",
"checkOut": "2026-11-11",
"guests": {
"total": 2,
"adults": 2,
"children": 0,
"infants": 0,
"pets": 0
},
"expectedPayout": {
"amount": 2152.6,
"currency": "USD"
},
"reservationId": null,
"relayedBy": null,
"respondBy": "2026-09-23T17:40:38.725Z",
"respondedAt": "2026-09-22T18:02:11.000Z",
"createdAt": "2026-09-22T17:40:39.000Z",
"updatedAt": "2026-09-22T18:02:11.000Z"
},
"previousAttributes": {
"status": "open"
},
"occurredAt": "2026-09-22T18:02:11.000Z",
"revision": "2026-09-22T18:02:11.000Z"
}
}Listings
listing.created
A new property was synced into Repull from a connected PMS or channel.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "6250",
"customerId": "1",
"channel": "airbnb",
"externalListingId": "1234567890123456789",
"name": "Lakeview Loft — Example City",
"active": true,
"listed": true,
"channels": [
{
"platform": "airbnb",
"externalId": "1234567890123456789",
"active": true,
"syncEnabled": true,
"syncCategory": "sync_all"
}
],
"address": {
"city": "Radium Hot Springs",
"region": "BC",
"country": "CA"
},
"bedrooms": 2,
"bathrooms": 2,
"maxGuests": 6,
"thumbnailUrl": "https://a0.muscache.com/im/pictures/…",
"createdAt": "2026-05-01T12:00:00.000Z",
"updatedAt": "2026-05-01T12:00:00.000Z"
},
"revision": "2026-05-01T12:00:00.000Z"
}
}listing.updated
Listing content, amenities, photos, or status changed.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "6250",
"customerId": "1",
"channel": "airbnb",
"externalListingId": "1234567890123456789",
"name": "Lakeview Loft — Example City",
"active": true,
"listed": true,
"channels": [
{
"platform": "airbnb",
"externalId": "1234567890123456789",
"active": true,
"syncEnabled": true,
"syncCategory": "sync_all"
}
],
"updatedAt": "2026-05-01T12:30:00.000Z"
},
"area": "content",
"previousAttributes": {
"name": "Lakeview Loft"
},
"revision": "2026-05-01T12:30:00.000Z"
}
}listing.deleted
A property was removed from Repull or the upstream PMS.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "6250",
"customerId": "1",
"channel": "airbnb",
"externalListingId": "1234567890123456789",
"name": "Lakeview Loft — Example City"
},
"reason": "unlinked",
"deletedAt": "2026-05-01T16:00:00.000Z"
}
}listing.suspended
The channel suspended the listing. Writes are still accepted and silently applied to nothing until it is reinstated.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "6250",
"customerId": "1",
"channel": "airbnb",
"externalListingId": "1234567890123456789",
"name": "Lakeview Loft — Example City",
"active": true,
"listed": false,
"channels": [
{
"platform": "airbnb",
"externalId": "1234567890123456789",
"active": true,
"syncEnabled": false,
"syncCategory": "none"
}
]
},
"reason": "quality_standards",
"occurredAt": "2026-05-01T17:00:00.000Z"
}
}listing.reactivated
The channel lifted a suspension and the listing accepts writes again.
listing.reactivatedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "6250",
"customerId": "1",
"channel": "airbnb",
"externalListingId": "1234567890123456789",
"name": "Lakeview Loft — Example City",
"active": true,
"listed": true,
"channels": [
{
"platform": "airbnb",
"externalId": "1234567890123456789",
"active": true,
"syncEnabled": true,
"syncCategory": "sync_all"
}
]
},
"reason": null,
"occurredAt": "2026-05-03T09:00:00.000Z"
}
}Calendar
calendar.updated
Availability or pricing for a listing was updated.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"listingId": "6250",
"range": {
"start": "2026-06-01",
"end": "2026-06-15"
},
"affectedDates": 14,
"pricingChanged": true,
"availabilityChanged": false
}
}Accounts
account.created
An OAuth or API credential connection was completed by an end user.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"workspaceId": "47f8883d-28c2-4d2c-b020-c7cef1aff62c",
"accountId": "acc_01HX5XPQ2K",
"provider": "airbnb",
"accessType": "full_access",
"createdAt": "2026-05-01T12:00:00.000Z"
}
}connect.session.completed
A user finished a Connect session, on any channel or PMS. Carries the session id, your state token and the account that was connected, so you can tie the connection to your own user.
connect.session.completedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"sessionId": "cs_abc123",
"state": "user_8421",
"provider": "vrbo",
"externalAccountId": "36",
"connectionId": null,
"purpose": "connect",
"completedAt": "2026-09-29T12:00:00.000Z"
}
}account.disconnected
A PMS or channel connection was revoked, expired, or rejected by the upstream provider. The reason field disambiguates: refresh_token_rejected, manual_disconnect, auth_expired, revoked_upstream.
account.disconnectedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"workspaceId": "47f8883d-28c2-4d2c-b020-c7cef1aff62c",
"accountId": "acc_01HX5XPQ2K",
"provider": "airbnb",
"disconnectedAt": "2026-05-01T17:00:00.000Z",
"reason": "refresh_token_rejected"
}
}Reviews
review.created
A new review was received on a reservation — from the guest (about the host/property) or the host (about the guest). reviewerRole disambiguates. Detected on the next review sync; providers do not push reviews in real time, so latency is bounded by the sync interval.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "90210",
"channel": "airbnb",
"listingId": "5668",
"reservationId": "215906",
"customerId": "1",
"reviewerRole": "guest",
"rating": 5,
"submittedAt": "2026-05-01T09:00:00.000Z",
"externalReviewId": "1548751920482971044",
"airbnbListingId": "2628",
"confirmationCode": "HMEXAMPLE1",
"publicReview": "Spotless flat, five minutes from the station. Taylor was quick to answer.",
"privateFeedback": "The second bedroom radiator was cold on the first night.",
"recommended": true,
"overallRating": 5,
"response": null,
"responded": false,
"respondByAt": "2026-05-15T09:00:00.000Z",
"hidden": false,
"createdAt": "2026-05-01T09:00:00.000Z",
"updatedAt": "2026-05-01T09:00:00.000Z"
},
"revision": "2026-05-01T09:00:00.000Z"
}
}review.responded
A public host response to a review was recorded. Fires when the review first appears with a response, or when a response is added to a previously unanswered review.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "90210",
"channel": "airbnb",
"listingId": "5668",
"reservationId": "215906",
"customerId": "1",
"reviewerRole": "guest",
"rating": 5,
"submittedAt": "2026-05-01T09:00:00.000Z"
},
"respondedAt": "2026-05-02T11:00:00.000Z"
}
}AI
ai.operation.completed
An async AI run (review response, message draft, pricing suggestion) finished.
ai.operation.completedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"operationId": "aiop_01HX5XPQ2K",
"type": "respond-to-guest",
"inputSummary": "Guest asked about parking",
"output": {
"message": "Free underground parking is included with your stay."
},
"tokensUsed": 184,
"completedAt": "2026-05-01T18:00:00.000Z"
}
}ai.operation.failed
An async AI run terminated with an error and will not be retried.
ai.operation.failedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"operationId": "aiop_01HX5XPQ2L",
"type": "price-suggestion",
"error": {
"code": "no_market_data",
"message": "Insufficient comparable listings."
},
"failedAt": "2026-05-01T18:01:00.000Z"
}
}Payments
payment.completed
A guest payment was successfully captured.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "277919",
"customerId": "1",
"transactionType": "payout",
"sourceType": "Payout",
"status": "completed",
"amount": "1792.17",
"currency": "CAD",
"platform": "airbnb",
"platformPaymentId": "G-FRSLYC3ZKAJDQ"
},
"completedAt": "2026-09-21T00:00:00.000Z",
"revision": "2026-09-21T04:12:08.000Z"
}
}payment.refunded
A previous payment was refunded in part or in full.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"object": {
"id": "278044",
"customerId": "1",
"transactionType": "adjustment",
"sourceType": "Resolution Adjustment",
"status": "completed",
"amount": "-215.40",
"currency": "GBP",
"reservationId": "215906",
"confirmationCode": "HMEXAMPLE1",
"listingId": "5668",
"platform": "airbnb",
"platformPaymentId": "RA-8832KDL"
},
"refundedAt": "2026-09-21T11:04:00.000Z",
"reason": "Resolution centre adjustment",
"revision": "2026-09-21T11:04:12.000Z"
}
}payout.completed
Airbnb paid the host. Sent once per payout, the first time Repull sees it, for payouts dated in the last 14 days. data.payout and data.lines are exactly what GET /v1/channels/airbnb/transactions?payout_id= returns for it, with the same transactionIds, so you can deduplicate across webhooks and reads. The lines' signed amounts sum to data.payout.payout.paidOutAmount. Payouts are found by the automatic refresh (about daily per account) or a POST /v1/channels/airbnb/transactions.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"channel": "airbnb",
"accountId": "10000001",
"accountName": "Seaside Stays",
"payout": {
"transactionId": "M-O5P2WERXKFVYH",
"accountId": "10000001",
"accountName": "Seaside Stays",
"status": "COMPLETED",
"type": "Payout",
"isPayout": true,
"date": "2026-09-24",
"currency": "USD",
"amount": 318.48,
"grossAmount": null,
"fees": {
"hostServiceFee": null,
"cleaningFee": null
},
"payout": {
"payoutId": "M-O5P2WERXKFVYH",
"payoutIdSynthetic": false,
"payoutDate": "2026-09-24",
"lineIndex": null,
"paidOutAmount": 318.48
},
"confirmationCode": null,
"reservationId": null,
"listingId": null,
"listingName": null,
"onInactiveListing": false,
"guestName": null,
"nights": null,
"reservationStartDate": null,
"description": "Account Holder, Checking 0000 (USD)",
"reference": null,
"unavailableFields": [
"taxes",
"guest_paid_total",
"original_transaction_id",
"currency_conversion",
"management_fee"
],
"syncedAt": "2026-09-25T06:37:12.000Z"
},
"lines": [
{
"transactionId": "M-O5P2WERXKFVYH:resolution_adjustment:HMMBM4BWTY:1",
"accountId": "10000001",
"accountName": "Seaside Stays",
"status": "COMPLETED",
"type": "Resolution Adjustment",
"isPayout": false,
"date": "2026-09-23",
"currency": "USD",
"amount": -250,
"grossAmount": null,
"fees": {
"hostServiceFee": null,
"cleaningFee": null
},
"payout": {
"payoutId": "M-O5P2WERXKFVYH",
"payoutIdSynthetic": false,
"payoutDate": "2026-09-24",
"lineIndex": 1,
"paidOutAmount": null
},
"confirmationCode": "HMMBM4BWTY",
"reservationId": "235812",
"listingId": "1234567890123456789",
"listingName": "Harbor Loft",
"onInactiveListing": false,
"guestName": "Alex Guest",
"nights": 4,
"reservationStartDate": "2026-09-02",
"description": "Resolution adjustment for resolution 17888008062543",
"reference": "17888008062543",
"unavailableFields": [
"taxes",
"guest_paid_total",
"original_transaction_id",
"currency_conversion",
"management_fee"
],
"syncedAt": "2026-09-25T06:37:12.000Z"
},
{
"transactionId": "M-O5P2WERXKFVYH:reservation:HMB2E9SR9P:1",
"accountId": "10000001",
"accountName": "Seaside Stays",
"status": "COMPLETED",
"type": "Reservation",
"isPayout": false,
"date": "2026-09-24",
"currency": "USD",
"amount": 568.48,
"grossAmount": 672.75,
"fees": {
"hostServiceFee": -104.27,
"cleaningFee": 120
},
"payout": {
"payoutId": "M-O5P2WERXKFVYH",
"payoutIdSynthetic": false,
"payoutDate": "2026-09-24",
"lineIndex": 2,
"paidOutAmount": null
},
"confirmationCode": "HMB2E9SR9P",
"reservationId": "235907",
"listingId": "1234567890123456789",
"listingName": "Harbor Loft",
"onInactiveListing": false,
"guestName": "Sam Guest",
"nights": 3,
"reservationStartDate": "2026-09-21",
"description": "Sep 21 – 24, 2026 HMB2E9SR9P",
"reference": null,
"unavailableFields": [
"taxes",
"guest_paid_total",
"original_transaction_id",
"currency_conversion",
"management_fee"
],
"syncedAt": "2026-09-25T06:37:12.000Z"
}
]
}
}Migrations
migration.completed
An import into a migration workspace finished. workspaceId (on the event) names the migration; data.results has what was processed per entity. Fires after the first import and after every re-run from POST /v1/migrations/{workspaceId}/import.
migration.completedreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"workspaceId": "1204",
"provider": "guesty",
"connectionId": "812",
"status": "completed",
"startedAt": "2026-09-24T18:02:11.000Z",
"finishedAt": "2026-09-24T18:06:47.000Z",
"entities": [
"listings",
"reservations"
],
"results": [
{
"entityType": "listings",
"processed": 48,
"errors": 0
},
{
"entityType": "reservations",
"processed": 1312,
"errors": 2
}
]
}
}migration.failed
An import into a migration workspace stopped with an error. The connection is kept — re-run it with POST /v1/migrations/{workspaceId}/import once the cause (usually the source PMS credentials) is fixed.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"workspaceId": "1204",
"provider": "guesty",
"connectionId": "812",
"status": "failed",
"startedAt": "2026-09-24T18:02:11.000Z",
"finishedAt": "2026-09-24T18:02:14.000Z",
"entities": [
"listings",
"reservations"
],
"error": "Guesty rejected the credentials (401). Reconnect the source and run the import again."
}
}System
repull.ping
A diagnostic delivery used by the dashboard to verify endpoint reachability.
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"message": "Ping from Repull. If you can read this, your endpoint is reachable."
}
}usage.quota.warning
Your account has used 80% of a request quota for the current window. Sent at most once per account per window — it is a heads-up, not a refusal. scope names which quota — today the only value is "daily_requests" — and topOperation names the operation driving the traffic so a runaway loop can be found before the cap stops it.
usage.quota.warningreference →
{
"event": "<event.name>",
"eventId": "3f1c9a2e-8b7d-4c6a-9e0f-1a2b3c4d5e6f",
"apiVersion": "2026-04",
"timestamp": "2026-05-01T12:34:56.000Z",
"data": {
"scope": "daily_requests",
"tier": "starter",
"used": 20000,
"limit": 25000,
"percentUsed": 80,
"remaining": 5000,
"resetsAt": "2026-05-02T00:00:00.000Z",
"topOperation": {
"operationId": "replay_webhook_delivery",
"requestCount": 17000,
"sharePercent": 85
}
}
}Subscribe
Create a webhook subscription with the events you want to receive. Repull will start delivering matching events to your URL immediately.
curl -X POST https://api.repull.dev/v1/webhooks \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/repull",
"events": [
"reservation.created",
"reservation.updated",
"reservation.cancelled",
"account.disconnected"
]
}'The field is `events`, not `event_types`
events array. Sending event_types is rejected with a 400 invalid_params ("eventsmust be a non-empty array").The response includes a signing_secret (whsec_...) you must store securely — it's used to verify the authenticity of every delivery.
Verify
Every delivery includes a timestamped X-Repull-Signature header of the form t=<unix_ts>,v1=<hex>, where v1 is HMAC-SHA256(signing_secret, `${t}.${raw_body}`). Recompute it, compare in constant time, and reject deliveries older than ~5 minutes. See Verify Signatures for full Node.js and Python examples.
Use the raw body
Ordering, Retries & Idempotency
- Ordering is best-effort, not strict. A
reservation.updatedand apayment.completedfor the same reservation may arrive out of order. Always reconcile against the latest API state when ordering matters. - Dedupe on
eventId. Failed deliveries are retried up to 5 times with exponential backoff. Every retry (and every manual replay) reuses the sameeventIdbut ships a newX-Repull-Delivery-Id— so dedupe oneventId, never on the delivery id. - Acknowledge fast. Return a 2xx status within 10 seconds. Slow handlers cause timeouts and unnecessary retries — push heavy work onto a background queue.
- Replay is supported. Any delivery from the last 7 days can be manually replayed from the dashboard or via the API. See Retries & Replay.
- Inactive listings send nothing. Events about an inactive listing are not delivered, and not held for later: activating the listing resumes delivery for events from then on. Replaying an older delivery for a listing that is inactive now returns
403 listing_inactive. Account-level events are always delivered. See Active & inactive listings.