# Guesty Webhooks

Guesty (property management for short-term rentals) sends webhooks through
Svix. Every request is signed with the endpoint's
signing secret and carries `svix-id`, `svix-timestamp`, and `svix-signature`
headers. The event name is in the body's `event` field (not `type`).

## When to Use This Skill

* How do I receive Guesty webhooks?
* How do I verify Guesty webhook signatures (`svix-signature`)?
* Where do I get the Guesty webhook secret?
* How do I handle `reservation.created.v2` / `reservation.updated.v2` events?
* How do I handle legacy `reservation.new` / `reservation.updated` events?
* Why is my Guesty webhook signature verification failing?
* How do I deduplicate Guesty webhooks?

## Verification (core)

Signed content is `${svix-id}.${svix-timestamp}.${raw_body}`, HMAC-SHA256 keyed
with the base64-decoded bytes after the `whsec_` prefix, base64-encoded. The
header holds one or more space-separated `v1,<signature>` entries. Use the
official `svix` SDK (Node and Python) — it handles all of that, the 5-minute
timestamp tolerance, and constant-time comparison:

```javascript
const { Webhook } = require('svix');

const wh = new Webhook(process.env.GUESTY_WEBHOOK_SECRET); // "whsec_..." from GET /webhooks-v2/secret
wh.verify(rawBody, {                                        // raw Buffer/string, never re-serialized JSON
  'svix-id': req.headers['svix-id'],
  'svix-timestamp': req.headers['svix-timestamp'],
  'svix-signature': req.headers['svix-signature'],
}); // throws WebhookVerificationError on a bad signature or stale timestamp

const event = JSON.parse(rawBody.toString('utf8'));
// event.event === 'reservation.created.v2', event.meta.eventId, event.data / event.reservation / ...

```

Python: `from svix.webhooks import Webhook` and call `Webhook(secret).verify(raw_body, headers)` the same way.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/guesty-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/guesty-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/guesty-webhooks/examples/fastapi/).

## Common Event Types

The payload is `{ "event": "<name>", "meta": { "eventId": ... }, <object>: {...} }`.
The object key depends on the event: `data` (reservation v2), `reservation`
(legacy reservation), `listing`, `guest`, `payment`.

| Event | Object key | Triggered When |
| --- | --- | --- |
| `reservation.created.v2` | `data` | A booking request or instant booking is created (recommended) |
| `reservation.updated.v2` | `data` | Stay details change; `meta.changedFields` / `meta.subType` say what changed |
| `reservation.new` | `reservation` | Legacy: booking created (being phased out by end of Feb 2027) |
| `reservation.updated` | `reservation` | Legacy: reservation, folio, or payment changed; also has `reservationBefore` |
| `listing.new` | `listing` | A listing is created |
| `listing.updated` | `listing` | A listing's settings or content change |
| `listing.removed` | `listing` | A listing is deleted |
| `guest.created` | `guest` | A guest record is created |
| `payments.received` | `payment` | A reservation payment succeeds |

Other events: `reservation_update_shortlist`, `calendar.updated.v2`,
`listing.calendar.updated`, `reservation.messageReceived`,
`reservation.messageSent`, `guest.updated`, `guest.deleted`, `task.created`,
`task.updated`, `task.deleted`, `guest-folio.created`, `guest-folio.updated`,
and the other `payments.*` events. See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/guesty-webhooks/references/overview.md).

## Environment Variables

```bash
# Per-endpoint signing secret (Svix default format: whsec_ + base64 key)
GUESTY_WEBHOOK_SECRET=whsec_xxxxx

```

Retrieve it with `GET https://open-api.guesty.com/v1/webhooks-v2/secret?url=<your endpoint URL>`
or from Guesty's webhooks developer dashboard (Developer tools → Webhooks).
Deleting and recreating a subscription can issue a new secret.

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 guesty --path /webhooks/guesty

```

Guesty subscriptions are created through the API (`POST /v1/webhooks`) and the
URL must be HTTPS on port 443, so register the Hookdeck tunnel URL.

## Key Gotchas

* **Raw body** — verify the exact bytes Guesty sent; parse JSON only afterwards.
* **Deduplicate** on `svix-id` and on `meta.eventId` (when present). Recovery processes can re-emit old events.
* **Out-of-order delivery** — order by `__v` / `lastUpdatedAt` (or `meta.publishedAt` on v2), and re-fetch the object from the API.
* **Respond 2xx within 15 seconds**; anything else (including 3xx) is retried on a schedule spanning about 28 hours.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/guesty-webhooks/references/overview.md) - Guesty webhook concepts, events, payloads
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/guesty-webhooks/references/setup.md) - Creating subscriptions via the API, getting the secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/guesty-webhooks/references/verification.md) - Signature verification details and gotchas