Skip to content

Webhooks overview

Webhooks push events to your server in real time, so you do not have to poll. When something happens in a restaurant — a reservation is created, a guest is updated, feedback comes in — we send an HTTP POST to a URL you control with a JSON event describing what happened.

Webhook endpoints are self-serve from the back office. An owner or co-owner can manage them under Settings → Developers:

  1. Add an endpoint with its URL — an HTTPS, publicly reachable address (internal or private hosts are rejected).
  2. Choose which events it should receive — individual event types or all events.
  3. Reveal its signing secret (whsec_…) and store it; you use it to verify signatures. You can rotate it later if it is ever exposed.

From the same screen you can enable, disable, or delete an endpoint, inspect a delivery log showing each attempt’s status and response, resend a past delivery, and send a test event. A restaurant can register up to 5 endpoints.

Each endpoint is pinned to an API version at creation, so the payload shape you receive stays stable.

An endpoint is registered by the restaurant and is not tied to an API key, so no scope filters what it receives. A reservation delivery carries the booking’s contact details (contact_email, contact_phone), the guest’s notes (guest_notes) and the restaurant’s private note (internal_notes), the guest.* events carry the guest profile, a waitlist entry carries the guest’s notes, and feedback.received carries the guest’s comment. On the API the same fields need guests:read: see Guest data on a booking. A waitlist entry’s private note is the one thing no payload carries.

Treat the endpoint URL like a credential to the restaurant’s guest book. Whoever answers at that URL receives every guest’s contact details as they change, so register only a host you control, and keep the URL out of tickets, chat and shared configuration.

Every delivery is a JSON object with this envelope:

{
"id": "evt_1K8xQ2m4Vd0pErJ7sN1aZ9bQ",
"object": "event",
"type": "reservation.updated",
"created": "2026-06-27T17:30:00Z",
"livemode": true,
"api_version": "2026-06-27",
"data": {
"object": { "object": "reservation", "id": "resv_…" },
"previous_attributes": { "party_size": 2 }
}
}

Every field on it, and the two clocks one payload carries, are on The webhook event object.

Return a 2xx status code to acknowledge receipt, then do the real work asynchronously. A delivery times out after 10 seconds, on the connection and on the read alike, so an endpoint that finishes its own processing before it answers starts failing the moment that processing is slow. Any non-2xx response, a timeout, or a connection error is a failed delivery and is retried.

Your response body is read up to 64 KiB and the read is then abandoned, which fails the delivery. The first 500 bytes are kept in the delivery log, so a short diagnostic string is worth returning and a full page of HTML is not.

Delivery is at-least-once. The same event may be delivered more than once (for example, if your server is slow to acknowledge and we retry, or after a transient network error). Deduplicate on the event id (evt_…): record the ids you have processed and ignore repeats. Make your handler idempotent.

Events are not guaranteed to arrive in the order they occurred. For example, you might receive reservation.seated before reservation.confirmed. Do not assume order. When a decision depends on current state, treat the event as a hint and fetch the latest resource from the API, or reconcile using each resource’s updated_at.

A failed delivery is retried on a fixed schedule: 9 attempts in all, spanning about 2.7 days. The waits between them are 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 24 hours and 24 hours. After the ninth attempt the delivery is marked failed and is never tried again.

One delivery that succeeds clears the endpoint’s failure count. 15 consecutive deliveries that exhaust all 9 attempts disable the endpoint, and an alert is raised. Fix the endpoint, re-enable it from the back office, then recover the events missed while it was down by re-fetching with updated_since.

Service publishes no list of IP addresses that deliveries come from, and commits to no fixed range. Authenticate a delivery by its signature, not by where it appears to come from.

Imported reservations don’t emit webhooks

Section titled “Imported reservations don’t emit webhooks”