Guide to Guesty Webhooks: Features and Best Practices
Guesty webhooks tell your systems when something changes in a short-term rental business: a booking is created or altered, a payment succeeds or fails, a guest sends a message, or a listing's calendar changes. If you run properties on Guesty and keep your own channel tools, owner portals, cleaning schedules or analytics in sync with it, webhooks are how those systems hear about changes without polling the Guesty Open API.
This guide covers how Guesty subscriptions are created and signed, the events you can subscribe to and how their payloads differ, the delivery rules that catch out new integrations, and the best practices for running a Guesty receiver in production.
What are Guesty webhooks?
A Guesty webhook subscription is a URL plus a list of event names, created through the Open API. When one of those events happens in your Guesty account, Guesty sends a JSON POST to the URL. Delivery runs on Svix, so every request carries three Svix headers: svix-id, svix-timestamp and svix-signature.
Guesty does not describe the signature scheme itself. Its docs tell you to retrieve your secret and then "Follow Svix's instructions" for validation. In practice that means the Svix scheme: a base64 HMAC-SHA256 over the message ID, the timestamp and the raw body, keyed with the decoded secret.
The event name is in the body's top-level event field. Beyond that field there is no common envelope: a v2 reservation event puts the reservation under data, a payment event uses payment, a guest event uses guest, and so on. Guesty also says its payloads "vary in their levels of detail", and recommends fetching the full object from the API after each webhook.
Guesty webhook features
| Feature | Details |
|---|---|
| Configuration | Open API only: POST https://open-api.guesty.com/v1/webhooks with a url and an events list |
| URL rules | HTTPS on port 443 with a publicly resolvable hostname. Keep each subscription's URL unique |
| Delivery platform | Svix |
| Signature headers | svix-id, svix-timestamp, svix-signature |
| Scheme | HMAC-SHA256, base64, over {svix-id}.{svix-timestamp}.{raw body} |
| Secret | One per subscribed endpoint, from GET /webhooks-v2/secret or the webhooks developer dashboard |
| Event name | Top-level event field in the body |
| Response window | A 2xx within about 15 seconds. Any other status, including 3xx, is a failure |
| Retries | Immediately, then 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours |
| Auto-disable | After 5 days of failures. Only Guesty API support can re-enable the endpoint |
| Ordering | Not guaranteed. Recovery processes can also re-emit events you already received |
| Replay | Failed messages can be replayed from the webhooks dashboard (Developer tools > Webhooks) |
Guesty publishes no source IP list for webhooks, so the signature is how you know a request is genuine.
Common events
Guesty's create-subscription reference lists 31 event names. They fall into eight domains:
| Domain | Events | Notes |
|---|---|---|
| Reservations (v2) | reservation.created.v2, reservation.updated.v2 | Lean payloads under data, with meta.eventId, meta.publishedAt and, on updates, meta.changedFields and meta.subType |
| Reservations (legacy) | reservation.new, reservation.updated, reservation_ | Full reservation object, plus reservationBefore on updates. Being phased out by the end of February 2027 |
| Messages | reservation.messageReceived, reservation.messageSent | Guest conversation activity on a reservation |
| Payments | payments.received, payments.failed, payments.refunded, payments.disputes, payments.overdue and six more | Payment lifecycle, authentication and payment method events, under payment |
| Listings | listing.new, listing.updated, listing.removed | Listing created, changed or deleted |
| Calendar | listing.calendar.updated, calendar.updated.v2 | Availability and pricing changes |
| Tasks | task.created, task.updated, task.deleted | Tasks created, updated or deleted |
| Guests and folios | guest.created, guest.updated, guest.deleted, guest-folio.created, guest-folio.updated | Guest records and reservation financials |
Guesty recommends the v2 reservation events. They separate stay details from financials and payments, which now have their own events (guest-folio.* and payments.*), and they add sequencing information. Here is Guesty's reservation.created.v2 example, trimmed:
{
"data": {
"accountId": "66053c21dd2adbdfb3b383b7",
"reservationId": "6a96ae8ea07eac3dcadf0aab",
"confirmationCode": "GY-pCCSraYC",
"status": "confirmed",
"checkIn": "2026-09-16T15:00:00.000Z",
"checkOut": "2026-09-20T10:00:00.000Z",
"guestId": "68b455b9e7d0e5d47378ac4b",
"guestsCount": 2,
"platform": "direct",
"source": "manual",
"unitId": "66054019764cbb000f37c450"
},
"event": "reservation.created.v2",
"meta": {
"eventId": "b708e831-d9fc-44d5-b94a-3ff72a0ef815",
"publishedAt": "2026-09-01T10:53:03.025Z"
}
}
A reservation.updated.v2 body has the same shape, and its meta adds what changed, for example "changedFields": ["notes"] and "subType": "NOTES_.
The calendar docs are inconsistent on one point. The field table for calendar.updated.v2 lists an event field, but its only example has no event key. Until you have seen one of these deliveries, don't assume every Guesty body carries event.
Setting up Guesty webhooks
Guesty has no dashboard form for creating subscriptions. You create them with the Open API, authenticated with a bearer token.
- Create the subscription with your HTTPS endpoint and the events you want:
curl -X POST "https://open-api.guesty.com/v1/webhooks" \
-H "Authorization: Bearer $GUESTY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/guesty",
"events": ["reservation.created.v2", "reservation.updated.v2", "payments.received"]
}'
- Fetch the signing secret for that endpoint and store it server-side, for example as
GUESTY_WEBHOOK_SECRET:
curl "https://open-api.guesty.com/v1/webhooks-v2/secret?url=https://example.com/webhooks/guesty" \
-H "Authorization: Bearer $GUESTY_ACCESS_TOKEN"
You can also read it from the webhooks developer dashboard in your Guesty account.
- To change the events later, call the update endpoint, which replaces the subscription's event list. The URL is fixed at creation and an update with a different
urlis rejected with a400, so moving to a new URL means creating a new subscription and deleting the old one.
A few rules apply. The URL must be HTTPS on port 443 with a public hostname, so a local server needs a tunnel. Don't create two subscriptions for the same URL: Guesty warns that this "can lead to missed notifications", and it is also the usual cause of duplicate payloads. Deleting and recreating a subscription can generate a new secret, so fetch it again whenever you do.
Securing Guesty webhooks
Verify every delivery before you parse it. Build the signed content as {svix-id}.{svix-timestamp}.{raw body}, compute an HMAC-SHA256 with the base64-decoded part of your secret after whsec_, base64-encode the result, and compare it in constant time with each v1, entry in svix-signature. The header can hold several space-separated entries, and a match on any one of them is valid.
const crypto = require('crypto');
const express = require('express');
function verifyGuestySignature(rawBody, headers, secret) {
const id = headers['svix-id'];
const timestamp = headers['svix-timestamp'];
const signatureHeader = headers['svix-signature'];
if (!id || !timestamp || !signatureHeader) return false;
if (typeof secret !== 'string' || !secret.startsWith('whsec_')) return false;
// The key is the base64-decoded part of the secret after "whsec_".
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const expected = crypto
.createHmac('sha256', key)
.update(`${id}.${timestamp}.`)
.update(rawBody)
.digest();
// The header can hold several space-separated "v1,<base64>" entries.
return signatureHeader.split(' ').some((entry) => {
const [version, signature] = entry.split(',');
if (version !== 'v1' || !signature) return false;
const received = Buffer.from(signature, 'base64');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
const app = express();
app.post('/webhooks/guesty', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.GUESTY_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyGuestySignature(req.body, req.headers, secret)) {
return res.sendStatus(401);
}
// Optional: if your own policy needs it, reject svix-timestamp values that are too old.
const event = JSON.parse(req.body.toString('utf8'));
// Route on event.event. Deduplicate on svix-id and event.meta?.eventId, then process async.
res.sendStatus(200);
});
The same check in Python:
import base64
import hashlib
import hmac
def verify_guesty_signature(raw_body: bytes, headers, secret: str) -> bool:
msg_id = headers.get("svix-id")
timestamp = headers.get("svix-timestamp")
signature_header = headers.get("svix-signature")
if not (msg_id and timestamp and signature_header):
return False
if not isinstance(secret, str) or not secret.startswith("whsec_"):
return False
key = base64.b64decode(secret[len("whsec_"):])
signed_content = f"{msg_id}.{timestamp}.".encode() + raw_body
expected = base64.b64encode(hmac.new(key, signed_content, hashlib.sha256).digest())
for entry in signature_header.split(" "):
version, _, signature = entry.partition(",")
if version == "v1" and hmac.compare_digest(signature.encode(), expected):
return True
return False
# In FastAPI: raw_body = await request.body()
# verify_guesty_signature(raw_body, request.headers, os.environ["GUESTY_WEBHOOK_SECRET"])
The official svix libraries for Node and Python do the same check in one call (new Webhook(secret).verify(rawBody, headers)). They also reject any timestamp more than five minutes from the current time, which matters if you ever re-run verification on a stored request.
To check your verifier, use the secret whsec_, the headers svix-id: msg_ and svix-timestamp: 1761040800, and this exact body:
{"event":"reservation.created.v2","meta":{"eventId":"b708e831-d9fc-44d5-b94a-3ff72a0ef815"}}
The svix-signature header should be:
v1,kTR5aMGocpNnSeCzwBzIQNRFT6BzmQesYWn7zCz83wk=
Make Guesty webhooks production-ready. Hookdeck Event Gateway verifies the Svix signature on every Guesty delivery, accepts it well inside the 15-second window, and durably queues reservation, payment and listing events before your service sees them.
Guesty webhook limitations and pain points
Events arrive out of order, and sometimes twice
The Problem: A reservation.updated can arrive before the event it follows, and a reservation you processed yesterday can show up again.
Why It Happens: Guesty says it "processes events asynchronously, so you may receive events out of order on occasion", and that recovery processes "may also emit historical events you've already received". Svix retries add more duplicates when your endpoint is slow or failing.
Workarounds:
- Deduplicate on
svix-idand, where present,meta.eventId, which is what Guesty recommends. - Order by
__vorlastUpdatedAtin the data, ormeta.publishedAton v2 events, never by arrival time. - Treat each webhook as a prompt to fetch the current object from the Open API, and let that drive your state.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on the fields you choose, such as meta.eventId, before they reach your service.
No common payload shape
The Problem: Code that reads body.data works for reservation.created.v2 and returns undefined for a payment or guest event.
Why It Happens: Every Guesty body has event, but each domain puts its object under its own key: data for v2 reservations, reservation for legacy ones, payment, guest, listing, and so on. Several domains publish only placeholder examples, so the exact fields are something you learn from real deliveries.
Workarounds:
- Map each event name to the key that holds its object, in one place, and read through that map.
- Log the first delivery of each event type you subscribe to and build your parser from what arrives.
- Fetch the full object from the API rather than relying on the webhook body for details.
How Hookdeck Can Help: Hookdeck filters route events to different destinations by the event field, and transformations can reshape each domain's body into a consistent structure before delivery.
Two generations of reservation events
The Problem: Integrations built on reservation.new and reservation.updated stop receiving events when the legacy events are retired.
Why It Happens: Guesty runs legacy and v2 reservation events side by side and says the legacy versions "will be phased out by the end of February 2027". The v2 events are shaped differently: they are leaner, and payments and folio changes moved out to their own events.
Workarounds:
- Subscribe to
reservation.created.v2andreservation.updated.v2in new integrations. The create-subscription reference already says the legacy events "may require temporary allowlist approval". - When migrating, add
payments.*andguest-folio.*subscriptions to replace the financial changes that legacyreservation.updatedused to carry. - Run both generations in parallel during migration and compare results before you drop the legacy subscription.
How Hookdeck Can Help: With both generations sent to one Hookdeck source, you can route legacy and v2 events to different destinations by event and replay stored events into the new handler while you test it.
A five-day outage turns the endpoint off
The Problem: After a long enough failure, Guesty stops delivering to your endpoint and you need Guesty support to turn it back on.
Why It Happens: Svix retries each message for about 27 hours. If every attempt to an endpoint fails for 5 days, the endpoint is automatically disabled, and Guesty says to contact Guesty API Support to reactivate it.
Workarounds:
- Return a
2xxas soon as the signature checks out and do the work afterwards. - Never return a redirect: Guesty counts
3xxas a failure. - Use the dashboard's Replay to resend failed messages once your endpoint is healthy again.
How Hookdeck Can Help: Hookdeck accepts each delivery as soon as it arrives and stores it, so Guesty sees a healthy endpoint even while your own service is down. You then retry and replay on your own schedule.
Best practices
Verify against the raw body
The signature covers the exact bytes Guesty sent. In Express, mount express.raw() on the webhook route and parse only after verification. In FastAPI, read await request.body(). Re-serialising parsed JSON changes the bytes and breaks the signature.
Keep one subscription per URL
Guesty warns that duplicate subscriptions for the same URL lead to missed notifications, and they are the first thing to check when you see duplicate payloads. Put every event you need in one subscription's events list, and only create separate subscriptions for separate systems.
Re-fetch after secret changes
Each endpoint has its own secret, and recreating a subscription can issue a new one. Read the secret with GET /webhooks-v2/secret?url=... after any change to a subscription, and update your receiver before the old deliveries stop verifying.
Treat webhooks as change notifications
Guesty's own handling steps are acknowledge, parse, validate, fetch and compare. Use the webhook to learn which object changed, fetch it from the Open API, and compare it with your stored copy. This handles lean v2 payloads, out-of-order delivery and re-emitted events with the same code.
Acknowledge fast and process asynchronously
Return a 2xx within the 15-second window and hand the event to a queue. Slow handlers produce retries, and retries produce duplicates. See why to process webhooks asynchronously.
Make handlers idempotent
Store svix-id and meta.eventId as you process them and skip repeats. See our guide to webhook idempotency.
Conclusion
Guesty sends its webhooks through Svix, so verification is the Svix scheme: an HMAC-SHA256 over svix-id, svix-timestamp and the raw body, keyed with your endpoint's decoded whsec_ secret. Subscribe through the Open API with one subscription per URL, move to the v2 reservation events before the legacy ones are retired, and expect events out of order or more than once.
Hookdeck Event Gateway sits in front of your receiver, checks every svix-signature, deduplicates repeated events, and holds each one until your service has processed it, so an outage on your side never runs into Guesty's five-day disable rule.
Get started with Hookdeck for free and handle Guesty webhooks reliably in minutes.