# Guide to Lodgix Webhooks: Features and Best Practices

Lodgix webhooks tell your systems when a guest sends a message about a booking. If you run vacation rentals on Lodgix and want guest messages in a shared inbox, a Slack channel, a CRM or an AI assistant, a webhook gets each message to you as it arrives instead of on a polling schedule.

This guide covers how to create a Lodgix webhook, how deliveries are signed and retried, and how to build a receiver that keeps up.

## What are Lodgix webhooks?

Lodgix webhooks are signed HTTP `POST` requests that Lodgix sends to an HTTPS endpoint you register in your account. There's one event today, `message.received`, and Lodgix says more event types may be added later.

Each endpoint has its own secret. Lodgix signs every request with HMAC-SHA256 over the `X-Lodgix-Timestamp` value, a period and the raw body, and sends the result as `X-Lodgix-Signature: sha256=<hex>`.

## Lodgix webhook features

| Feature | Details |
| --- | --- |
| Configuration | **Modules > Integrations > Webhooks** in Lodgix |
| Endpoint URL | Must start with `https://` |
| Events | `message.received` |
| Signature header | `X-Lodgix-Signature`, in the form `sha256=<hex>` |
| Timestamp header | `X-Lodgix-Timestamp`, Unix seconds when the request was signed |
| Signed content | `<timestamp>.<raw body>` |
| Secret | One per endpoint |
| Event header | `X-Lodgix-Event` |
| Delivery ID | `X-Lodgix-Delivery` |
| Success response | Any `2xx` |
| Retries | 5 retries, at 30 seconds, 5 minutes, 30 minutes, 2 hours and 6 hours |
| Delivery log | Each delivery records its last HTTP status and error |
| Auto-disable | An endpoint that keeps failing can be disabled. **Re-enable** is on the endpoint card |

## Common events

| Event | Fires when |
| --- | --- |
| `message.received` | A guest message is received |

Read the event name from `X-Lodgix-Event` so your receiver is ready for new event types. Lodgix doesn't publish the body of a `message.received` delivery, so log the first few real deliveries before you map fields.

## Setting up Lodgix webhooks

1. In Lodgix, go to **Modules > Integrations > Webhooks**.
2. Click **Create Webhook**, or **Add Endpoint** if you already have one.
3. Give the endpoint a name you'll recognise later.
4. Enter the endpoint URL. It must start with `https://`.
5. Select **Message Received** (`message.received`).
6. Click **Create Endpoint**, then copy the endpoint's secret and store it server-side, for example as `LODGIX_WEBHOOK_SECRET`.

If you receive Lodgix webhooks through Hookdeck, use the Hookdeck source URL as the endpoint URL, create a source with the Lodgix type and paste the endpoint's secret. The Lodgix source accepts `POST` only, which is how Lodgix delivers webhooks.

## Securing Lodgix webhooks

Read `X-Lodgix-Timestamp` and `X-Lodgix-Signature`. Build the message from the timestamp, a period and the raw body bytes, compute an HMAC-SHA256 with the endpoint secret, and compare the hex digest with the value after `sha256=` in constant time.

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

function verifyLodgixSignature(rawBody, signatureHeader, timestampHeader, secret, toleranceSeconds = 0) {
  if (!signatureHeader || !timestampHeader || !secret) return false;

  const hex = signatureHeader.startsWith('sha256=') ? signatureHeader.slice(7) : signatureHeader;
  if (!/^[0-9a-fA-F]{64}$/.test(hex)) return false;

  // Optional replay window. Off by default (see below).
  if (toleranceSeconds > 0) {
    const timestamp = Number(timestampHeader);
    if (!Number.isFinite(timestamp)) return false;
    if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  }

  const message = Buffer.concat([Buffer.from(timestampHeader, 'latin1'), Buffer.from('.'), rawBody]);
  const expected = crypto.createHmac('sha256', secret).update(message).digest();
  const received = Buffer.from(hex, 'hex');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/lodgix', express.raw({ type: '*/*' }), (req, res) => {
  const secret = process.env.LODGIX_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500); // fail closed

  const valid = verifyLodgixSignature(
    req.body,
    req.get('x-lodgix-signature'),
    req.get('x-lodgix-timestamp'),
    secret
  );
  if (!valid) return res.sendStatus(401);

  const eventType = req.get('x-lodgix-event');
  // Deduplicate on the X-Lodgix-Delivery header, route on eventType, then process asynchronously.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hashlib
import hmac
import re
import time

def verify_lodgix_signature(raw_body: bytes, signature_header, timestamp_header, secret: str,
                            tolerance_seconds: int = 0) -> bool:
    if not signature_header or not timestamp_header or not secret:
        return False
    digest = signature_header[7:] if signature_header.startswith("sha256=") else signature_header
    if not re.fullmatch(r"[0-9a-fA-F]{64}", digest):
        return False
    # Optional replay window. Off by default (see below).
    if tolerance_seconds > 0:
        try:
            timestamp = float(timestamp_header)
        except ValueError:
            return False
        if abs(time.time() - timestamp) > tolerance_seconds:
            return False
    message = timestamp_header.encode("latin-1") + b"." + raw_body
    expected = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).digest()
    return hmac.compare_digest(bytes.fromhex(digest), expected)

# In FastAPI: raw_body = await request.body()
# verify_lodgix_signature(raw_body, request.headers.get("x-lodgix-signature"),
#                         request.headers.get("x-lodgix-timestamp"),
#                         os.environ["LODGIX_WEBHOOK_SECRET"])

```

Lodgix tells you to reject a request whose timestamp is more than five minutes old. The samples above make that check optional and leave it off by default. Hookdeck applies no timestamp tolerance, and its retries and replays resend Lodgix's original headers, so a five-minute window on your endpoint would reject any delivery Hookdeck retries later than that. If your endpoint receives directly from Lodgix, pass `toleranceSeconds = 300` to follow Lodgix's advice.

Lodgix also documents an n8n recipe: turn on **Raw Body** in the Webhook trigger node, extract the body as text, and compute the HMAC with a Crypto node over `{{ $('Webhook').item.json.headers['x-lodgix-timestamp'] }}.{{ $json.body_text }}`, output as hex, before comparing `sha256=` plus that value with the header.

To check your verifier, use the secret `lodgix-guide-secret`, an `X-Lodgix-Timestamp` of `1761480000` and this exact body:

```
{"event":"message.received"}

```

The header should be:

```
X-Lodgix-Signature: sha256=4be0339d1f0afc0d5a6aae977527f5c71ae44ed0be2fee0db20fbd02c01af576

```

> **Make Lodgix webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) verifies the `X-Lodgix-Signature` header on every delivery, returns a `2xx` straight away, and queues each guest message until your service has processed it.

## Lodgix webhook limitations and pain points

### A short retry schedule and auto-disable

**The Problem:** Guest messages stop arriving after an outage, and the endpoint shows as disabled.

**Why It Happens:** Lodgix retries a failed delivery five times over about eight and a half hours, then marks it **Exhausted**. An endpoint that keeps failing can be disabled automatically and stays off until someone re-enables it.

**Workarounds:**

* Return `2xx` as soon as the request is verified and stored, and do the work asynchronously.
* Check the delivery log on the endpoint after an incident. It records the last status code and error.
* Use **Re-enable** on the endpoint card once your endpoint is fixed.

**How Hookdeck Can Help:** Hookdeck acknowledges each delivery as soon as it arrives and stores it, then delivers to your service with [retries](/docs/retries) you configure, so your own downtime doesn't count towards Lodgix's failures.

### A five-minute window that fights retries

**The Problem:** Following Lodgix's advice to reject old timestamps makes late retries and replays fail.

**Why It Happens:** `X-Lodgix-Timestamp` is when the request was signed. A delivery replayed or retried by something between Lodgix and your code keeps the original header.

**Workarounds:**

* Apply the window only where requests come straight from Lodgix.
* Rely on the delivery ID for duplicate protection behind a proxy.

**How Hookdeck Can Help:** Hookdeck verifies the signature when the request first arrives and applies no timestamp tolerance, so a late retry isn't marked as failed. If your service keeps its own window behind Hookdeck, make it wide enough for Hookdeck's retries, or leave it off.

### An undocumented body

**The Problem:** You can't build a parser for guest messages from the docs alone.

**Why It Happens:** Lodgix documents the headers and signature but not the `message.received` body.

**Workarounds:**

* Point a test endpoint at a request inspector and send yourself a guest message.
* Route on `X-Lodgix-Event`, not on a body field, until you know the body's shape.

**How Hookdeck Can Help:** Hookdeck stores every request, headers and body, so the first real delivery becomes the reference for your parser.

## Best practices

### Verify against the raw body

Lodgix warns that re-serialising the JSON changes the signature. In Express, use `express.raw({ type: '*/*' })`. In Next.js, use `Buffer.from(await request.arrayBuffer())`. In FastAPI, use `await request.body()`.

### Deduplicate deliveries

Store each `X-Lodgix-Delivery` value you've processed and skip repeats. Lodgix calls it a unique ID for the delivery. Check one retried request to confirm the ID stays the same across attempts. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency) and Hookdeck's [deduplication](/docs/deduplication).

### Plan for new event types

Lodgix expects to add events. Route on `X-Lodgix-Event`, return `2xx` for types you don't handle yet, and log them.

## Conclusion

Lodgix webhooks are simple to set up: one event, an HTTPS endpoint and a `sha256=` signature over the timestamp and raw body. The parts to plan for are the short retry schedule, automatic disabling, and a five-minute timestamp rule that only suits requests coming straight from Lodgix.

[Hookdeck Event Gateway](https://hookdeck.com) sits in front of your receiver, checks every Lodgix signature, and holds each guest message until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Lodgix webhooks reliably in minutes.