# Hostaway Webhooks

Hostaway sends a webhook when something changes in your account: a reservation is created or updated, a guest message arrives, a task changes. Webhooks are **not signed**. If you set a login and password on the webhook (Settings > Integrations, unified webhook, or the legacy reservation and message webhooks), Hostaway sends them as HTTP Basic auth, and your endpoint authenticates the request by checking the `Authorization` header.

> **Set a login and password on the Hostaway webhook. Webhooks without them send nothing to verify.**

## When to Use This Skill

* How do I receive Hostaway webhooks?
* How do I verify a Hostaway webhook? (HTTP Basic auth, there is no signature)
* Why does my Hostaway webhook endpoint return 401?
* How do I handle `reservation.created`, `reservation.updated` or `message.received` events?
* Which HTTP method does Hostaway use for webhooks?

## Verification (core)

```javascript
const crypto = require('crypto');

const sha = (s) => crypto.createHash('sha256').update(s).digest(); // equal-length buffers
const safeEqual = (a, b) => crypto.timingSafeEqual(sha(a), sha(b));

function verifyHostawayRequest(headers, user = process.env.HOSTAWAY_WEBHOOK_USERNAME,
                               pass = process.env.HOSTAWAY_WEBHOOK_PASSWORD) {
  if (!user || !pass) return false;                          // never accept when unconfigured
  const m = /^basic +([A-Za-z0-9+/]+={0,2})$/i.exec((headers.authorization || '').trim());
  if (!m) return false;                                      // scheme is case-insensitive
  const buf = Buffer.from(m[1], 'base64');
  if (buf.toString('base64') !== m[1]) return false;         // strict: no junk after valid base64
  const text = buf.toString('utf8');
  const i = text.indexOf(':');                               // split on the FIRST colon
  if (i === -1) return false;
  const userOk = safeEqual(text.slice(0, i), user);          // evaluate both, no short-circuit
  const passOk = safeEqual(text.slice(i + 1), pass);
  return userOk && passOk;
}

```

* Header: `Authorization: Basic base64(username:password)`, built from the login and password you set on the webhook. Both are optional in Hostaway; without them there is nothing to verify.
* Missing or malformed header, or a mismatch: return `401`. Check the credential first, for every HTTP method, before reading the body.
* Compare the username and the password each in constant time. Node: `crypto.timingSafeEqual` on equal-length buffers (SHA-256 digests here). Python: `hmac.compare_digest` on **bytes** (encode both sides; `str` raises on non-ASCII). Decode strictly (`validate=True` in Python) so valid credentials followed by junk are rejected.
* **A static shared credential authenticates the sender, not the body.** There is no signature and no timestamp, so it gives no replay protection. Use HTTPS only, and rotate the login and password in Hostaway if they leak.
* **Hookdeck never sends a 401 challenge** (no `WWW-Authenticate` round trip), so the credentials must arrive with every request. Don't rely on the sender retrying with credentials after a challenge.
* Read the body raw (`express.raw({ type: '*/*' })`) so any content type works, and parse it only after the check.

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

## HTTP Methods and Body

Hostaway's docs don't name the HTTP method or the body format. The examples therefore accept **GET, POST, PUT, PATCH and DELETE** on the webhook route (Express `app.all`, a Next.js handler per method, FastAPI `api_route`), check the credential first for every method, and handle an empty body. Hookdeck's Hostaway source pre-selects POST without managing the method, so **any other method the sender uses must also be allowed on the source**.

## Event Types

The unified webhook payload is not documented field by field, so treat the payload below as **illustrative** and read the event name defensively (`event`, `eventType`, or `object` + `action`), logging unknown shapes.

| Event | Sent When |
| --- | --- |
| `reservation.created` | A reservation is created |
| `reservation.updated` | A reservation changes |
| `message.received` | A guest message arrives |
| `task.created` | A task is created |
| `task.updated` | A task changes |

```json
{ "event": "reservation.created", "data": { "id": 1001, "listingMapId": 2002, "guestName": "Jane Doe", "status": "new" } }

```

Hostaway times out after 20 seconds and retries transient failures up to 3 times, so acknowledge quickly and process idempotently. Only ports 80 and 443 are reachable.

## Environment Variables

```bash
HOSTAWAY_WEBHOOK_USERNAME=your-webhook-login      # the login you set on the Hostaway webhook
HOSTAWAY_WEBHOOK_PASSWORD=your-webhook-password   # the password you set on the Hostaway webhook

```

## Local Development

Hostaway can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 hostaway --path /webhooks/hostaway

```

Use the printed URL as the webhook URL in Hostaway.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hostaway-webhooks/references/overview.md) - What Hostaway webhooks are, events and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hostaway-webhooks/references/setup.md) - Set the webhook, login and password, and allowed methods
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hostaway-webhooks/references/verification.md) - Basic auth check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/hostaway-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests