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

Webhooks

Webhook events

7 events, each a pointer to a record. When one arrives, read the current state from the API. The Webhooks guide covers receiving and verifying them.

Events

EventWhendata
listing.updatedA property was put on the website, or its changes were published.
{
  "propertyId": "0d6e1c2a-…"
}
listing.deletedA property was taken off the website or deleted. 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, usually to undo a data-entry mistake. A cancellation is an update.
{
  "reservationId": "7c02…",
  "propertyId": "0d6e1c2a-…",
  "status": "option",
  "previousStatus": null
}
pingSomeone pressed the test button under Settings › Integrations.
{
  "orgSlug": "acme",
  "sentAt": "2026-09-13T10:00:00.000Z"
}

The envelope

The body is the delivery id, the event, createdAt, and a small data object that points at the record.

Headers

HeaderValue
x-quarters-eventThe event name, the same as event in the body.
x-quarters-deliveryThe delivery id, the same as id in the body. Dedupe on it.
x-quarters-timestampWhen the delivery was sent, in Unix seconds.
x-quarters-signaturesha256= followed by the hex HMAC-SHA256, under your endpoint’s secret, of the timestamp, a dot and the raw body.

The signature covers the timestamp and the raw body. Verify it in constant time, and refuse a timestamp more than five minutes from your clock. Each delivery also carries x-housalot-event, x-housalot-delivery and x-housalot-signature, the headers’ former names, whose signature covers the body alone. They go away with notice.

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. Recent deliveries and their outcomes are listed under the endpoint in Settings › Integrations. A full read of the resource is the recovery.
  • An endpoint that resolves to a private address is refused on delivery, not on save.

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";

const TOLERANCE_SECONDS = 5 * 60;

// rawBody is the request body exactly as received, before any JSON parsing.
export function verify(secret, rawBody, headers) {
  const timestamp = headers["x-quarters-timestamp"] ?? "";
  if (!/^\d+$/.test(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;
  const expected = `sha256=${createHmac("sha256", secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest("hex")}`;
  const actual = headers["x-quarters-signature"] ?? "";
  return actual.length === expected.length &&
    timingSafeEqual(Buffer.from(actual), Buffer.from(expected));
}

Verify · Python

import hashlib
import hmac
import time

TOLERANCE_SECONDS = 5 * 60

# raw_body is the request body exactly as received, before any JSON parsing.
def verify(secret: str, raw_body: bytes, headers) -> bool:
    timestamp = headers.get("x-quarters-timestamp") or ""
    if not timestamp.isdigit():
        return False
    if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
        return False
    signed = timestamp.encode() + b"." + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(headers.get("x-quarters-signature") or "", expected)