Webhook Event Types

TL;DR

Repull delivers 15 event types across reservations, listings, calendar, connections, 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 connection.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 connection.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.
reservation.cancelledA reservation was cancelled by the guest, host, or platform.
reservation.message.receivedA new inbound message arrived on a reservation thread.
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.
Calendarcalendar.updatedAvailability or pricing for a listing was updated.
Connectionsconnection.createdAn OAuth or API credential connection was completed by an end user.
connection.disconnectedA PMS or channel connection was revoked or expired.
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.
Systemrepull.pingA diagnostic delivery used by the dashboard to verify endpoint reachability.

Delivery Envelope

Every webhook delivery uses the same outer JSON envelope. Only the data field changes per event type. The deliveryId is unique per HTTP attempt — use it for idempotency dedupe (retries reuse the same id).

{
  "type": "reservation.created",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": { /* per-event payload, see sections below */ }
}

Reservations

reservation.created

A new reservation arrived from any connected channel or direct booking.

When it fires: Fires once when a new reservation lands in Repull from a channel sync, OAuth-linked PMS push, direct-booking website, or manual creation. Will not fire again on subsequent edits — see reservation.updated.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "id": 215906,
    "confirmationCode": "HMA1234567",
    "listingId": 6250,
    "platform": "airbnb",
    "status": "confirmed",
    "checkIn": "2026-06-01",
    "checkOut": "2026-06-05",
    "nights": 4,
    "guests": {
      "adults": 2,
      "children": 0,
      "infants": 0
    },
    "primaryGuest": {
      "firstName": "Alex",
      "lastName": "Morgan",
      "email": "alex@example.com"
    },
    "pricing": {
      "subtotal": "1200.00",
      "taxes": "120.00",
      "total": "1320.00",
      "currency": "USD"
    },
    "createdAt": "2026-05-01T12:34:56.000Z"
  }
}

Notes: Idempotent on `id` — if you receive the same reservation id twice (e.g. from a retry), treat the second delivery as a no-op.

reservation.updated

Dates, guest count, status, or pricing changed on an existing reservation.

When it fires: Fires when any meaningful field on a reservation changes after creation — date shift, guest count change, status transition (e.g. confirmed → checked-in), pricing adjustment, or assigned-listing change.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "id": 215906,
    "confirmationCode": "HMA1234567",
    "changes": {
      "checkOut": {
        "from": "2026-06-05",
        "to": "2026-06-07"
      },
      "pricing": {
        "from": {
          "total": "1320.00"
        },
        "to": {
          "total": "1640.00"
        }
      }
    },
    "updatedAt": "2026-05-01T13:00:00.000Z"
  }
}

Notes: The `changes` object only contains fields that actually changed. Diff against your last-known state — do not assume any specific field will be present.

reservation.cancelled

A reservation was cancelled by the guest, host, or platform.

When it fires: Fires once when a reservation transitions to a cancelled status. The reservation row remains queryable via the API afterwards — `status: "cancelled"`.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "id": 215906,
    "confirmationCode": "HMA1234567",
    "cancelledAt": "2026-05-01T14:00:00.000Z",
    "cancelledBy": "guest",
    "reason": "guest_requested",
    "refund": {
      "amount": "1320.00",
      "currency": "USD"
    }
  }
}

reservation.message.received

A new inbound message arrived on a reservation thread.

When it fires: Fires for every new inbound message from a guest on a reservation thread, across every connected platform (Airbnb, Booking.com, direct, SMS, email).

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "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"
  }
}

Notes: Only fires for inbound (guest → host) messages. Outbound replies sent through the API do not trigger this event.

Listings

listing.created

A new property was synced into Repull from a connected PMS or channel.

When it fires: Fires once per new listing the first time Repull observes it during a sync. Re-importing or re-syncing an existing listing will not refire.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "id": 6250,
    "title": "R-Sable 1302 — Radium Hot Springs",
    "address": {
      "city": "Radium Hot Springs",
      "region": "BC",
      "country": "CA"
    },
    "bedrooms": 2,
    "bathrooms": 2,
    "maxGuests": 6,
    "createdAt": "2026-05-01T12:00:00.000Z"
  }
}

listing.updated

Listing content, amenities, photos, or status changed.

When it fires: Fires when listing-level fields change — title, description, photos, amenities, base price, max guests, listing status (active/inactive).

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "id": 6250,
    "changes": {
      "title": {
        "from": "R-Sable 1302",
        "to": "R-Sable 1302 — Radium Hot Springs"
      }
    },
    "updatedAt": "2026-05-01T12:30:00.000Z"
  }
}

Notes: Calendar-only changes (date-range pricing, blocked dates) fire `calendar.updated` instead.

listing.deleted

A property was removed from Repull or the upstream PMS.

When it fires: Fires when a listing is deactivated or removed. The listing record is soft-deleted — historical reservations remain queryable.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "id": 6250,
    "deletedAt": "2026-05-01T16:00:00.000Z",
    "reason": "deactivated_by_owner"
  }
}

Calendar

calendar.updated

Availability or pricing for a listing was updated.

When it fires: Fires when a date-range update changes availability or pricing for a listing — bulk price overrides, blocked dates, manual calendar edits, or upstream sync deltas.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "listingId": 6250,
    "range": {
      "start": "2026-06-01",
      "end": "2026-06-15"
    },
    "affectedDates": 14,
    "pricingChanged": true,
    "availabilityChanged": false
  }
}

Notes: Coalesced — many small calendar mutations within a short window may collapse into one event covering the union range. Re-fetch the calendar for the listing if you need exact per-day state.

Connections

connection.created

An OAuth or API credential connection was completed by an end user.

When it fires: Fires the moment an end user finishes the Connect flow and Repull has a valid token / credential set for their PMS or OTA account.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "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"
  }
}

Notes: Use this signal to flip your UI from "Connect required" to "Connected" without polling. The first sync of listings/reservations begins immediately after this event fires.

connection.disconnected

A PMS or channel connection was revoked or expired.

When it fires: Fires when an OAuth connection is revoked by the user, expired by the upstream provider, or rejected by the upstream refresh-token endpoint. Does NOT fire for transient network failures or rate-limit errors.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "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": "revoked_by_user"
  }
}

Notes: The connection cannot be repaired server-side once this fires. Your end user must complete the OAuth flow again. Surface a "Reconnect" CTA the moment you receive this event.

AI

ai.operation.completed

An async AI run (review response, message draft, pricing suggestion) finished.

When it fires: Fires when an asynchronous AI operation that you submitted via the API finishes successfully. The `output` shape depends on the operation `type`.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "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.

When it fires: Fires once for an AI operation that exhausted its retry budget or hit an unrecoverable error (no upstream model, invalid input, missing context).

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "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.

When it fires: Fires after a guest payment is fully captured (not just authorised). For multi-payment reservations, this fires once per captured installment.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "id": "pay_01HX5XPQ2K",
    "reservationId": 215906,
    "amount": "1320.00",
    "currency": "USD",
    "method": "card",
    "capturedAt": "2026-05-01T12:35:00.000Z"
  }
}

payment.refunded

A previous payment was refunded in part or in full.

When it fires: Fires when a refund is processed against a previously completed payment. Partial refunds fire once per refund, not once per cumulative balance change.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "id": "pay_01HX5XPQ2K",
    "refundId": "rfn_01HX5XPQ2K",
    "reservationId": 215906,
    "amount": "1320.00",
    "currency": "USD",
    "refundedAt": "2026-05-01T19:00:00.000Z"
  }
}

System

repull.ping

A diagnostic delivery used by the dashboard to verify endpoint reachability.

When it fires: Fires only when triggered manually — from the dashboard "Send test" button or from `POST /v1/webhooks/{id}/ping`. Never fires automatically.

{
  "type": "<event.type>",
  "deliveryId": "evt_01HX5XPQ2K",
  "createdAt": "2026-05-01T12:34:56.000Z",
  "data": {
    "message": "Ping from Repull. If you can read this, your endpoint is reachable."
  }
}

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",
    "event_types": [
      "reservation.created",
      "reservation.updated",
      "reservation.cancelled",
      "connection.disconnected"
    ]
  }'

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 an X-Repull-Signature header containing an HMAC-SHA256 of the raw request body computed with your subscription's signing secret. Always verify it before processing the event — see Verify Signatures for full Node.js and Python examples.

Use the raw body

Verify the HMAC against the raw request body — 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.
  • Retries use the same deliveryId. Failed deliveries are retried up to 5 times with exponential backoff. Dedupe on deliveryId.
  • Acknowledge fast. Return a 2xx status within 5 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.
AI