Docs/Webhooks/Reservation

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 (Airbnb, Booking.com, Plumguide), an OAuth-linked PMS push, a direct-booking website, or a manual API call. It will not fire again on subsequent edits — those raise reservation.updated.

Payload

Every delivery uses the same outer envelope (event, eventId, apiVersion, timestamp, data). Dedupe on eventId — it stays stable across retries and replays, while the X-Repull-Delivery-Id header changes on every attempt.

{
  "event": "reservation.created",
  "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"
  }
}

Verifying signatures

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}`). Verify it before processing — 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.

Example handler

// Express handler — verify signature, then route on type
app.post('/webhooks/repull', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifyRepullSignature(req)) return res.sendStatus(401)
  const { type, deliveryId, data } = JSON.parse(req.body.toString())

  if (type === 'reservation.created') {
    // Idempotent on deliveryId — retries reuse the same id
    if (alreadyProcessed(deliveryId)) return res.sendStatus(200)
    onNewReservation(data) // sync to your CRM, notify the cleaner, etc.
  }

  res.sendStatus(200)
})

Common patterns

  • Treat the payload as a hint, not the source of truth. Always re-fetch the full reservation via GET /v1/reservations/{id} before charging cards or sending guest comms — the canonical record may have additional fields the payload trims.
  • Dedupe on deliveryId. Retries reuse the same id, so a simple seen-set or unique index on deliveryId is enough to make your handler idempotent.
  • Subscribe to reservation.created together with reservation.updated and reservation.cancelled. The three events form the full lifecycle — alone, reservation.created leaves you blind to date shifts and cancellations.

Tip: Acknowledge with a 2xx status within 10 seconds. Failed deliveries are retried up to 5 times with exponential backoff.Webhook reliability →

AI