Skip to content
Quarters Developers
esc
  • Type an endpoint, an object or a word from a guide.

Webhooks

Webhook events

Eight events, each a pointer to a record. Read the current state from the API when one arrives; that is what makes a late or repeated delivery harmless. How to receive and verify them is in the Webhooks guide.

Events

EventWhendata
listing.updatedA property was published, or re-published with changes.
{
  "propertyId": "0d6e1c2a-…"
}
listing.deletedA property was unpublished or removed. Drop it from your site.
{
  "propertyId": "0d6e1c2a-…"
}
calendar.updatedAvailability changed for a property: a booking, a cancellation, a block.
{
  "propertyId": "0d6e1c2a-…"
}
reservation.createdA booking was created, in any status.
{
  "reservationId": "7c02…",
  "propertyId": "0d6e1c2a-…",
  "status": "reserved",
  "previousStatus": null
}
reservation.updatedA booking changed: its status, dates, guest, money or notes.
{
  "reservationId": "7c02…",
  "propertyId": "0d6e1c2a-…",
  "status": "confirmed",
  "previousStatus": "reserved"
}
reservation.deletedA booking was deleted outright, which is a data-entry mistake being undone. A cancellation is an update.
{
  "reservationId": "7c02…",
  "propertyId": "0d6e1c2a-…",
  "status": "inquiry",
  "previousStatus": null
}
agreement.requestedStaff pressed Request contract on a booking while another system signs the contracts. Issue the envelope and report it back. force is true when a live envelope should be re-issued.
{
  "reservationId": "7c02…",
  "propertyId": "0d6e1c2a-…",
  "force": false
}
pingSomebody pressed the test button in settings. Confirms the endpoint and the signature.
{
  "orgSlug": "cityretreat",
  "sentAt": "2026-09-13T10:00:00.000Z"
}

The envelope

The body is the delivery id, the event, when it was createdAt, and a small data object that points at the record. It is a pointer, not the record.

Headers

HeaderValue
x-housalot-eventThe event name, the same as event in the body.
x-housalot-deliveryThe delivery id, the same as id in the body. Dedupe on it.
x-housalot-signaturesha256= followed by the hex HMAC-SHA256 of the raw body under your endpoint’s secret.

The header names carry the product’s former name and will be renamed only with notice and a transition period.

Delivery

  • One POST per event per endpoint, over https, with a ten-second timeout, following no redirects.
  • Any 2xx counts as received. Anything else, a timeout or a refused connection is logged as failed.
  • There is no retry queue. The last deliveries and their outcomes are listed under the endpoint in Settings › Integrations, and a full read of the resource is always the recovery.
  • Endpoints that resolve to a private address are refused at connection time, not at save time.

Delivery body

{
  "id": "6f1c…",
  "event": "reservation.updated",
  "createdAt": "2026-09-13T10:00:00.000Z",
  "data": {
    "reservationId": "7c02…",
    "propertyId": "0d6e1c2a-…",
    "status": "confirmed",
    "previousStatus": "reserved"
  }
}

Verify · Node

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody is the request body exactly as received, before any JSON parsing.
export function verify(secret, rawBody, signatureHeader) {
  const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
  const actual = signatureHeader ?? "";
  return actual.length === expected.length &&
    timingSafeEqual(Buffer.from(actual), Buffer.from(expected));
}

Verify · Python

import hashlib
import hmac

# raw_body is the request body exactly as received, before any JSON parsing.
def verify(secret: str, raw_body: bytes, signature_header: str | None) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature_header or "", expected)