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
- In Lodgix, go to Modules > Integrations > Webhooks.
- Click Create Webhook, or Add Endpoint if you already have one.
- Give the endpoint a name you'll recognise later.
- Enter the endpoint URL. It must start with
https://. - Select Message Received (
message.received). - 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.
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:
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_, 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 verifies the
X-Lodgix-Signatureheader on every delivery, returns a2xxstraight 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
2xxas 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 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 and Hookdeck's 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 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 for free and handle Lodgix webhooks reliably in minutes.