What do webhook events send?
Every event a webhook address can receive (bookings, waitlist, tickets, customers, loyalty), an example message, what is and isn't included, how to check the signature, and what happens when your tool is down.
Your webhook addresses (Settings → Data and policies → Integrations → Configure on Zapier) each receive only the events you tick. This guide lists every event and shows what a message looks like, so you can map the fields in Zapier or Make before a real booking arrives — or press Send test on an address to receive a made-up example of any event. Webhooks are part of the Complete plan.
The events
Bookings
- reservation.created — Booking created: a new booking is made. (A waitlist entry or a ticket order has its own event instead.)
- reservation.confirmed — Booking confirmed: a booking that was waiting for approval is confirmed.
- reservation.modified — Booking changed: the date, time, party size, length or table changes. The message lists each change with its old and new value — use it to keep a calendar or spreadsheet in step.
- reservation.cancelled — Booking cancelled.
- reservation.seated — Booking seated (also sent when a waitlist guest is seated).
- reservation.cleared — Booking cleared: the table is cleared after the visit.
- reservation.noshow — Booking no-show.
Waitlist
- waitlist.joined — Joined the waitlist.
- waitlist.table_ready — Table ready: the guest is told their table is ready.
- waitlist.left — Left the waitlist: the entry ended without being seated. reason says cancelled, removed or no_show.
Tickets (one message per order, not per ticket)
- ticket.purchased — Tickets bought.
- ticket.cancelled — Tickets cancelled: the order is cancelled or refunded.
- ticket.checked_in — Checked in at the door: people on the order are let in. peopleIn says how many are in so far and everyoneIn whether the whole order is in — a group of four arriving two at a time sends two messages.
Customers
- customer.created — New customer: a guest's customer record is first made.
- customer.marketing_consent_changed — Marketing choice changed: a guest says yes or no to your marketing emails or texts. Use it so your mailing tool honours an opt-out the same day.
Loyalty
- loyalty.joined — Joined the loyalty scheme (when the membership becomes active, not while it waits for the guest to confirm their email).
- loyalty.left — Left the loyalty scheme.
Booking seated and Booking cleared only fire if your team marks bookings that way — see How do I see what ResoFlow is connected to?.
An example message
Every message is a JSON POST with the same outer shape:
{ "id": "evt_3f9a1c0b2d4e5f6a7b8c9d0e", "type": "reservation.modified", "apiVersion": "2026-11-01", "created": "2026-11-14T18:02:11.000Z", "venue": { "id": "the-exchequer", "name": "The Exchequer" }, "data": { "booking": { "id": "res_8Kq2", "date": "2026-11-21", "time": "19:30", "partySize": 4, "durationMinutes": 120, "status": "confirmed", "tables": ["Table 12"], "source": "online", "preference": "Window", "updatedAt": "2026-11-14T18:02:10.000Z" }, "changes": [ { "field": "time", "from": "19:00", "to": "19:30" } ], "customer": { "id": "0123456789abcdef0123456789abcdef" }, "guest": { "name": "Sam Lee", "email": "[email protected]", "phone": "+447700900123" } } }
- id is the same on every retry of the same event — keep it and ignore a repeat.
- data holds booking, waitlistEntry, ticketOrder, customer and loyaltyMember depending on the event; ticket orders carry their total as amountPence and currency (GBP), and keep the same id for their purchase, cancellation and check-in.
- customer.id is your venue's own code for the guest: the same guest always has the same code at your venue, so you can match messages without using their email. It is made with a secret key that belongs to your venue, so the same guest has a different code at any other venue and nobody can work it out from an email address.
- Every object has updatedAt — messages can arrive out of order, so drop one older than what you already have.
- Tables are sent by name, never by an internal id.
What is and isn't included
- Guest contact details (guest — name, email, phone) are included unless you switch off Include guest contact details on the address.
- Booking notes (notes) are only included if you switch on Include booking notes — notes can carry allergies, so leave it off unless your tool needs them.
- A change to these switches also reaches messages still waiting to be retried: switch contact details or notes off and they're taken out before the next try.
- Never sent: the booking's allergy and dietary fields (an allergy typed into the notes goes with the notes), ticket codes, gift card codes, loyalty card codes, booking and payment links, staff names and card details.
The tool you send to receives this data because you set it up — it's your choice which tools get it, and you're responsible for how they use it.
Checking the signature
Each address has its own signing secret (shown once when you add it). Every message carries:
- X-ResoFlow-Signature — t=<seconds since 1970>,v1=<signature> (for 24 hours after you rotate the secret it carries two v1 values — one per secret — and either one is valid)
- X-ResoFlow-Event — the event name
- X-ResoFlow-Event-Id — the same id as in the body
- X-ResoFlow-Webhook-Version — 2026-11-01
To check it: join the t value, a full stop and the raw request body; work out an HMAC-SHA256 of that with your secret; compare it with each v1 using a constant-time compare; and reject anything whose t is more than five minutes old. In Node:
const crypto = require("node:crypto"); function verifyResoFlow(rawBody, header, secret) { const pairs = header.split(",").map((p) => p.split("=")); const t = Number((pairs.find(([k]) => k === "t") [])[1]); if (!t Math.abs(Date.now() / 1000 - t) > 300) return false; // replay guard const expected = Buffer.from(crypto.createHmac("sha256", secret).update(${t}.${rawBody}).digest("hex"), "hex"); return pairs.filter(([k]) => k === "v1").some(([, v]) => { const got = Buffer.from(v, "hex"); return got.length === expected.length && crypto.timingSafeEqual(got, expected); }); }
Use the raw body exactly as received — re-serialising the JSON changes the bytes and the check fails. Zapier's Catch Hook can't check a signature without a Code step; Make's Custom webhook can with a filter or a script.
When your tool is down
- Your tool should answer with any 2xx status within 10 seconds.
- A timeout, a connection error (including the address's name not being found for a moment), 408, 429 or a 5xx is tried again — after about 30 seconds, then 1, 2, 4, 8 and 16 minutes, then every 4 hours until 24 hours have passed. A Retry-After header is honoured, up to 4 hours.
- Any other 4xx isn't retried (the same message would get the same answer), and a redirect (3xx) is never followed.
- 410 Gone means your tool has stopped listening — the address is switched off at once.
- An address that accepts nothing for three days is switched off, and the owner is emailed. Fix the tool, then press Turn back on on the address. Events that happened while it was off aren't sent later. Turning an address off, removing it, unticking an event or changing its web address also stops any retries still waiting for it — they're never sent to the new address.
Related: How do I see what ResoFlow is connected to? · Running ResoFlow as a booking diary