Developer reference
Webhooks
A restaurant can set a webhook URL in Settings, Notifications. TableHelm sends one signed POST the instant an online reservation is created. This page is the exact wire format.
Event
Only one event exists today, reservation.created. It fires for reservations made through your public booking page (source "online"). Reservations you enter yourself at the host stand, or take over the phone, never trigger it.
Payload
Fields are intentionally limited: enough to act on, not the guest's contact details or the link that cancels their booking.
{
"event": "reservation.created",
"event_version": 1,
"created": "2026-09-10T23:04:11.482Z",
"data": {
"reservation_id": "6e9f8e2a-1a3c-4f2e-9b0a-2f6d7e0b1c4d",
"source": "online",
"guest_name": "Naomi Chen",
"party_size": 4,
"service_date": "2026-09-10",
"slot_time": "19:00"
}
}Headers and signature
Every request carries:
X-TableHelm-Event, the event name.X-TableHelm-Timestamp, unix seconds the request was signed at.X-TableHelm-Signature,sha256=<hex>, an HMAC-SHA256 of{timestamp}.{raw request body}keyed with your signing secret (Settings, Notifications, Webhook).
Verify the signature over the raw body before parsing JSON, and reject anything with a timestamp more than 5 minutes old, even if the signature checks out.
const crypto = require("crypto");
function isValidTableHelmWebhook(secret, rawBody, headers) {
const timestamp = Number(headers["x-tablehelm-timestamp"]);
const signature = headers["x-tablehelm-signature"]; // "sha256=<hex>"
if (!timestamp || !signature) return false;
// Reject anything outside a 5 minute window, even if the signature is valid.
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Delivery semantics
This is best-effort, at-most-once delivery: one POST attempt with a 5 second timeout, fired right after the reservation is created. There is no retry queue. If your endpoint is down, slow, or returns a non-2xx status, that event is not redelivered. Design your receiver to be fast and to accept the event on any 2xx response, and treat this as a nice-to-have notification channel, not a system of record. The reservation itself, and the guest's own confirmation, do not depend on this in any way.
Set it up
Add your webhook URL, generate a signing secret, and send a test event from Settings, Notifications.