Webhooks
Webhooks push changes to you within seconds, removing most of the polling an
integration would otherwise need. Manage them yourself with the
webhooks:manage scope — no email to us required.
Register an endpoint
Code
The response contains the signing secret, shown once. Store it before you close the connection; it cannot be retrieved later, only rotated.
Your URL must be HTTPS with a public hostname. Literal IP addresses, private ranges and loopback are rejected — both when you register and again at delivery time, so a hostname that starts resolving to a private address later stops being delivered to.
Events
| Event | Fires when |
|---|---|
reservation.created | A booking arrives, from any channel |
reservation.updated | A booking changes |
reservation.cancelled | A booking is cancelled |
reservation.checkout | A stay reaches its check-out |
block.created / block.updated / block.deleted | An availability block changes |
calendar.updated | Prices or stay restrictions change |
property.updated | Any property edit |
Deliveries inherit your grant: you will never receive an event for a property outside your property scope, or for a workspace that has revoked you.
property.updated fires on every property write, including bulk edits of
amenities, equipment and internal fields. The one deliberate exception is a CSV
bulk import, which is summarised rather than emitting an event per row —
reconcile after an import with updated_after on GET /v1/properties.
Payload
Code
Payloads carry ids, not full objects. Fetch the record if you need its current state — that way you always act on what is true now, rather than on a snapshot that may be several retries old.
Verify the signature
Every delivery is signed:
Code
The signed material is <timestamp>.<raw request body>, HMAC-SHA256 with your
signing secret.
Code
Two things matter here. Verify against the raw body bytes, before any JSON parsing — re-serialising changes the bytes and the signature will not match. And compare in constant time, as above.
Rotating the secret
POST /v1/webhooks/{id}/rotate-secret issues a new secret while the previous
one stays valid for 24 hours. Accept both during that window (the secrets
array above), deploy the new value, then let the old one lapse. No dropped
deliveries.
Retries and idempotency
Respond 2xx quickly — ideally by enqueuing the event and returning
immediately. Anything else is a failure and is retried after 1 minute, 5
minutes, 30 minutes, 2 hours and 12 hours. After that the delivery is
exhausted, and you can replay it from
POST /v1/webhooks/{id}/deliveries/{deliveryId}/retry.
An endpoint that keeps failing is disabled automatically, and both you and
the workspace are notified. Re-enable it with PATCH /v1/webhooks/{id} once it
is healthy.
Delivery is at-least-once: if you time out after processing an event, we
cannot know that and will retry. Deduplicate on the event id, which is stable
across every retry.
Catching up after downtime
GET /v1/events is the durable log:
Code
Use it to fill a gap without a full resync. And keep a periodic
updated_after reconciliation running regardless:
webhooks are the optimisation, polling remains the guarantee.
Debugging
GET /v1/webhooks/{id}/deliveries shows recent attempts with the response code
and error we recorded. That is usually enough to distinguish "our request never
arrived" from "your endpoint returned 500".

