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

Subscribe to 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

DomainEventDescription
Reservationsreservation.createdA new reservation arrived from any connected channel or direct booking.
reservation.updatedDates, 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.cancelledA reservation was cancelled by the guest, host, or platform.
reservation.message.sentA 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.updatedA 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.receivedA new inbound message arrived on a reservation thread.
reservation.alteration.createdA 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.respondedA pending reservation alteration was accepted, declined, or cancelled. The status field on the object reflects the new state.
reservation.request.createdA 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.updatedA 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.
Inquiriesinquiry.createdA 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.updatedAn 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.
Listingslisting.createdA new property was synced into Repull from a connected PMS or channel.
listing.updatedListing content, amenities, photos, or status changed.
listing.deletedA property was removed from Repull or the upstream PMS.
listing.suspendedThe channel suspended the listing. Writes are still accepted and silently applied to nothing until it is reinstated.
listing.reactivatedThe channel lifted a suspension and the listing accepts writes again.
Calendarcalendar.updatedAvailability or pricing for a listing was updated.
Accountsaccount.createdAn OAuth or API credential connection was completed by an end user.
connect.session.completedA 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.disconnectedA 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.
Reviewsreview.createdA 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.respondedA 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.
AIai.operation.completedAn async AI run (review response, message draft, pricing suggestion) finished.
ai.operation.failedAn async AI run terminated with an error and will not be retried.
Paymentspayment.completedA guest payment was successfully captured.
payment.refundedA previous payment was refunded in part or in full.
payout.completedAirbnb 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.
Migrationsmigration.completedAn 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.failedAn 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.
Systemrepull.pingA diagnostic delivery used by the dashboard to verify endpoint reachability.
usage.quota.warningYour 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.

FieldWhat you get
object.cancellationPolicyThe 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.checkInTimeLocal check-in time, HH:MM on a 24-hour clock in the property's timezone — not UTC, and not yours. null when unknown.
object.checkOutTimeLocal 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.

FieldAirbnbWhere to get it instead
Guest emailNot 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 methodNot 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 referenceNo 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.
NotesNot 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).

inquiry.createdreference →

{
  "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.

inquiry.updatedreference →

{
  "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.

listing.createdreference →

{
  "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.

listing.updatedreference →

{
  "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.

listing.deletedreference →

{
  "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.

listing.suspendedreference →

{
  "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.

calendar.updatedreference →

{
  "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.

account.createdreference →

{
  "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.

review.createdreference →

{
  "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.

review.respondedreference →

{
  "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.

payment.completedreference →

{
  "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.

payment.refundedreference →

{
  "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.

payout.completedreference →

{
  "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.

migration.failedreference →

{
  "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.

repull.pingreference →

{
  "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`

Subscriptions are created with an 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

Sign the raw request body exactly as received — not a re-stringified JSON object. Re-serialisation can reorder keys or change whitespace and break the signature.

Ordering, Retries & Idempotency

  • Ordering is best-effort, not strict. A reservation.updated and a payment.completed for 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 same eventId but ships a new X-Repull-Delivery-Id — so dedupe on eventId, 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.
AI