Event catalog
These are the event types you can subscribe to. Each event uses the standard
envelope; the examples below show
the full envelope with a representative data.object.
Handle unknown event types gracefully — new types may be added over time.
Summary
Section titled “Summary”| Event | When it fires |
|---|---|
reservation.created |
A reservation is created, or on a guaranteed slot once its card is saved — its status is confirmed (auto-confirmed) or pending (awaiting approval). |
reservation.pending |
A confirmed reservation was changed, by the guest or by a PATCH, in a way that needs re-approval. |
reservation.confirmed |
A pending reservation is later approved. Auto-confirmed reservations do not fire this. |
reservation.declined |
A pending reservation is declined. |
reservation.updated |
A reservation’s details change (party size, date, notes, …). |
reservation.partially_seated |
Some, but not all, of the party has been seated. |
reservation.seated |
The party has been seated. |
reservation.completed |
The reservation has finished (the party has left). |
reservation.cancelled |
The reservation is cancelled. |
reservation.no_show |
The party did not arrive. |
guest.created |
A guest record is created. |
guest.updated |
A guest’s details change. |
guest.deleted |
A guest is deleted (e.g. data-protection erasure). |
guest.merged |
Two guest profiles were merged. The absorbed id now resolves to the survivor. |
guest.unmerged |
A merge was undone and the absorbed profile restored. |
reservation.guarantee_requested |
A card guarantee was requested for a reservation. |
reservation.guarantee_charged |
The card was charged — fully or partially. |
reservation.guarantee_refunded |
A charge was refunded, fully or partially. |
reservation.guarantee_released |
The guarantee ended with no charge. |
reservation.guarantee_payment_failed |
A charge attempt failed. |
feedback.received |
A guest submits feedback after a visit. |
waitlist_entry.created |
A guest joined the waitlist. |
waitlist_entry.promoted |
A waitlist entry became a reservation. |
waitlist_entry.updated |
A guest changed their waitlist request — the window, the party size, or both. |
waitlist_entry.cancelled |
A waitlist entry was withdrawn by the guest or the restaurant. |
waitlist_entry.expired |
A waitlist entry ran out of time and was closed. |
hold.created |
A slot was held. The table is occupied, and nobody has booked it. |
hold.converted |
A hold became the reservation it names. Count it as the hold being released. |
hold.released |
A hold was handed back before its deadline. |
hold.expired |
A hold ran out of time. |
Reservation events
Section titled “Reservation events”The data.object of every reservation event is a full reservation, in the
same shape as the API reference returns — the complete
resource snapshot, not a diff, guest contact details and notes included (see
Deliveries carry guest data).
It always carries company: the company the
booking is made for, as { "object": "company", "id": "cmp_…", "name": … }, or
null.
Only reservation.updated carries
data.previous_attributes; the other reservation events omit it. When present,
previous_attributes contains only the changed fields among party_size,
service_date, guest_notes, internal_notes, excluded_from_shift_limits,
guest (the previous guest as a bare gst_ id) and company (the previous
company object, or null when there was none). company is left out when the previous company has since been
deleted. Time changes are reflected in the snapshot’s starts_at /
ends_at, not in previous_attributes.
reservation.created
Section titled “reservation.created”A reservation is created (via the widget, the back office, an integration, a
waitlist promotion or a hold conversion). It fires once per booking — check
data.object.status to tell them apart: confirmed means it was auto-confirmed at creation (no approval step;
typical for back-office and default bookings), while pending means it needs
approval and will later emit reservation.confirmed or
reservation.declined.
Three kinds of booking do not fire it at creation:
- A booking on a slot that carries a card guarantee. The event is withheld
until the guest saves the card, until the restaurant accepts the booking
without one, or until a
PATCHtakes the card requirement away, and it fires then. No otherreservation.*event about the booking is sent before it. If the card is never saved and the booking is cancelled,reservation.creatednever fires, and neither doesreservation.cancelled. See Guaranteed bookings arrive out of order. - A hold. Taking one fires
hold.created, not this; converting it fires both. See A conversion fires two events. - A reservation imported from another booking system when the restaurant
moved to Service. Imports fire no
reservation.*webhook.
{ "id": "evt_1K8xQ2m4Vd0pErJ7sN1aZ9bQ", "object": "event", "type": "reservation.created", "created": "2026-06-20T09:04:18Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "pending", "source": "widget", "booking_channel": "website_widget", "party_size": 4, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table if possible", "internal_notes": null, "guest": "gst_3Td9Lp0WqZ", "company": null, "tables": [], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-20T11:04:18+02:00" } }}reservation.pending
Section titled “reservation.pending”A reservation that was already confirmed has been changed in a way that
needs the restaurant to approve it again — so it has returned to pending. The
change can come from the guest on their own booking page, or from a
PATCH /v1/reservations/{id} through this API: a bigger party, or a move to a
service where the restaurant approves bookings.
A change to a booking that is already pending does not fire this event. It
arrives as reservation.updated, and the booking stays
pending.
{ "id": "evt_H8iJ9kL0mN1oP2qR3sT4uV5w", "object": "event", "type": "reservation.pending", "created": "2026-07-14T09:22:31Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_DA2ipZ7Enk01lpIhd0saMQ1e", "status": "pending", "source": "widget", "booking_channel": "website_widget", "party_size": 4, "service_date": "2026-07-18", "starts_at": "2026-07-18T20:00:00+02:00", "ends_at": "2026-07-18T22:00:00+02:00", "guest_notes": null, "contact_phone": "+33612345678", "internal_notes": null, "guest": "gst_FD7tTyOA9c-ZpPpeF0XMhkZI", "company": null, "tables": [], "guarantee": null, "metadata": {}, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-14T11:22:31+02:00" } }}reservation.confirmed
Section titled “reservation.confirmed”A pending reservation is approved — the manual-approval flow. This fires
only for reservations created as pending; an auto-confirmed reservation
never emits reservation.confirmed: it arrives once, as
reservation.created with status: "confirmed".
{ "id": "evt_2M9yR3n5We1qFsK8tO2bA0cR", "object": "event", "type": "reservation.confirmed", "created": "2026-06-20T09:30:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "confirmed", "source": "widget", "booking_channel": "website_widget", "party_size": 4, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table if possible", "internal_notes": null, "guest": "gst_3Td9Lp0WqZ", "company": null, "tables": [], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-20T11:30:00+02:00" } }}reservation.declined
Section titled “reservation.declined”A pending reservation is declined.
{ "id": "evt_3N0zS4o6Xf2rGtL9uP3cB1dS", "object": "event", "type": "reservation.declined", "created": "2026-06-20T09:31:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "cancelled", "source": "widget", "booking_channel": "website_widget", "party_size": 4, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table if possible", "internal_notes": null, "guest": "gst_3Td9Lp0WqZ", "company": null, "tables": [], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-20T11:31:00+02:00" } }}reservation.updated
Section titled “reservation.updated”A reservation’s details change — for example party size or notes.
previous_attributes lists the changed fields and their previous values (here,
the party size grew from 4 to 6, the guest note changed, the restaurant wrote a
note of its own, and the booking was put on a company’s account). A change to
internal_notes alone fires this event like any other change.
{ "id": "evt_4O1aT5p7Yg3sHuM0vQ4dC2eT", "object": "event", "type": "reservation.updated", "created": "2026-06-25T07:12:55Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "confirmed", "source": "widget", "booking_channel": "website_widget", "party_size": 6, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table; celebrating an anniversary", "internal_notes": "Regulars — the corner banquette if it is free", "guest": "gst_3Td9Lp0WqZ", "company": { "object": "company", "id": "cmp_7Hk2Rw9XsB", "name": "Atelier Morel" }, "tables": [], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-25T09:12:55+02:00" }, "previous_attributes": { "party_size": 4, "guest_notes": "Window table if possible", "internal_notes": null, "company": null } }}reservation.partially_seated
Section titled “reservation.partially_seated”Some, but not all, of the party has been seated.
{ "id": "evt_5P2bU6q8Zh4tIvN1wR5eD3fU", "object": "event", "type": "reservation.partially_seated", "created": "2026-06-27T18:05:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "partially_seated", "source": "widget", "booking_channel": "website_widget", "party_size": 6, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table if possible", "internal_notes": null, "guest": "gst_3Td9Lp0WqZ", "company": null, "tables": [ { "object": "table", "id": "tbl_QpZ2", "name": "12", "min_capacity": 2, "max_capacity": 6, "section": { "object": "section", "id": "sec_Lm8", "name": "Main room", "area_type": "indoor" } } ], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-27T20:05:00+02:00" } }}reservation.seated
Section titled “reservation.seated”The party has been seated.
{ "id": "evt_6Q3cV7r9Ai5uJwO2xS6fE4gV", "object": "event", "type": "reservation.seated", "created": "2026-06-27T18:08:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "seated", "source": "widget", "booking_channel": "website_widget", "party_size": 6, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table if possible", "internal_notes": null, "guest": "gst_3Td9Lp0WqZ", "company": null, "tables": [ { "object": "table", "id": "tbl_QpZ2", "name": "12", "min_capacity": 2, "max_capacity": 6, "section": { "object": "section", "id": "sec_Lm8", "name": "Main room", "area_type": "indoor" } } ], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-27T20:08:00+02:00" } }}reservation.completed
Section titled “reservation.completed”The reservation has finished — the party has left.
{ "id": "evt_7R4dW8s0Bj6vKxP3yT7gF5hW", "object": "event", "type": "reservation.completed", "created": "2026-06-27T20:15:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "completed", "source": "widget", "booking_channel": "website_widget", "party_size": 6, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table if possible", "internal_notes": null, "guest": "gst_3Td9Lp0WqZ", "company": null, "tables": [], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-27T22:15:00+02:00" } }}reservation.cancelled
Section titled “reservation.cancelled”The reservation is cancelled.
{ "id": "evt_8S5eX9t1Ck7wLyQ4zU8hG6iX", "object": "event", "type": "reservation.cancelled", "created": "2026-06-26T16:00:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "cancelled", "source": "widget", "booking_channel": "website_widget", "party_size": 6, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table if possible", "internal_notes": null, "guest": "gst_3Td9Lp0WqZ", "company": null, "tables": [], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-26T18:00:00+02:00" } }}reservation.no_show
Section titled “reservation.no_show”The party did not arrive.
{ "id": "evt_9T6fY0u2Dl8xMzR5aV9iH7jY", "object": "event", "type": "reservation.no_show", "created": "2026-06-27T18:45:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ", "status": "no_show", "source": "widget", "booking_channel": "website_widget", "party_size": 6, "service_date": "2026-06-27", "starts_at": "2026-06-27T20:00:00+02:00", "ends_at": "2026-06-27T22:00:00+02:00", "guest_notes": "Window table if possible", "internal_notes": null, "guest": "gst_3Td9Lp0WqZ", "company": null, "tables": [], "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-27T20:45:00+02:00" } }}Guarantee events
Section titled “Guarantee events”A card guarantee is a payment card held against a reservation — an imprint that is charged only if the guest does not honour the booking, under the restaurant’s own no-show policy.
These five ids are reservation.* because the data.object is the
reservation, carrying the guarantee embedded under guarantee. But they are
emitted from the guarantee’s own lifecycle rather than the reservation’s, so
they do not appear in the GET /v1/reservations/{id}/events feed, which
lists reservation events only. Webhooks are the way to observe them.
The money fields are integers in the currency’s minor unit — 5000 is €50.00.
A request that ends before any card is saved goes to state: "withdrawn" and
fires none of the five: the booking was cancelled, the restaurant withdrew the
request, or a change made the card unnecessary. Nothing was charged. You read
the state on the reservation.
reservation.guarantee_requested
Section titled “reservation.guarantee_requested”The restaurant asked the guest for a card on a booking that already exists. The
booking goes back to awaiting_guarantee, the guarantee’s state is
awaiting_card, and no money has moved. origin is
staff_request, card and consented_at are null until the guest saves a
card, and expires_at is the payment deadline.
A create through this API on a guaranteed slot does not fire this event. That
booking is not published until the card is saved or the request is withdrawn, so
you learn about it from reservation.created, with the
guarantee already active, or withdrawn when a PATCH took the card
requirement away. Its guarantee’s origin is staff_request too: the card is
asked for by the same pay-link.
{ "id": "evt_L1mN2oP3qR4sT5uV6wX7yZ8a", "object": "event", "type": "reservation.guarantee_requested", "created": "2026-07-11T09:12:04Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_1M__exJ2ZQ7jKENZxz28djsH", "status": "awaiting_guarantee", "source": "widget", "booking_channel": "website_widget", "party_size": 2, "service_date": "2026-07-18", "starts_at": "2026-07-18T20:00:00+02:00", "ends_at": "2026-07-18T22:00:00+02:00", "guest_notes": null, "contact_phone": "+33612345678", "internal_notes": null, "guest": "gst_e_I4WiTGNl0S1v7W2G6M6ygk", "company": null, "tables": [], "guarantee": { "object": "guarantee", "kind": "imprint", "state": "awaiting_card", "origin": "staff_request", "amount": 5000, "currency": "EUR", "charged_amount": 0, "refunded_amount": 0, "card": null, "cancel_deadline_at": "2026-07-17T20:00:00+02:00", "expires_at": "2026-07-13T11:12:04+02:00", "consented_at": null, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-11T11:12:04+02:00" }, "metadata": {}, "created_at": "2026-07-09T18:40:12+02:00", "updated_at": "2026-07-11T11:12:04+02:00" } }}reservation.guarantee_charged
Section titled “reservation.guarantee_charged”The card was charged.
{ "id": "evt_M2nO3pQ4rS5tU6vW7xY8zA9b", "object": "event", "type": "reservation.guarantee_charged", "created": "2026-07-18T20:31:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_1M__exJ2ZQ7jKENZxz28djsH", "status": "no_show", "source": "widget", "booking_channel": "website_widget", "party_size": 2, "service_date": "2026-07-18", "starts_at": "2026-07-18T20:00:00+02:00", "ends_at": "2026-07-18T22:00:00+02:00", "guest_notes": null, "contact_phone": "+33612345678", "internal_notes": null, "guest": "gst_e_I4WiTGNl0S1v7W2G6M6ygk", "company": null, "tables": [], "guarantee": { "object": "guarantee", "kind": "imprint", "state": "charged", "origin": "online_booking", "amount": 5000, "currency": "EUR", "charged_amount": 5000, "refunded_amount": 0, "card": { "brand": "visa", "last4": "4242" }, "cancel_deadline_at": "2026-07-17T20:00:00+02:00", "expires_at": null, "consented_at": "2026-07-11T11:12:04+02:00", "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T22:31:00+02:00" }, "metadata": {}, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T22:31:00+02:00" } }}reservation.guarantee_refunded
Section titled “reservation.guarantee_refunded”A charge was refunded, in whole or in part. refunded_amount is the cumulative
total refunded, not the amount of this refund — a second partial refund raises
it again rather than emitting a delta.
{ "id": "evt_N3oP4qR5sT6uV7wX8yZ9aB0c", "object": "event", "type": "reservation.guarantee_refunded", "created": "2026-07-19T08:00:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_1M__exJ2ZQ7jKENZxz28djsH", "status": "no_show", "source": "widget", "booking_channel": "website_widget", "party_size": 2, "service_date": "2026-07-18", "starts_at": "2026-07-18T20:00:00+02:00", "ends_at": "2026-07-18T22:00:00+02:00", "guest_notes": null, "contact_phone": "+33612345678", "internal_notes": null, "guest": "gst_e_I4WiTGNl0S1v7W2G6M6ygk", "company": null, "tables": [], "guarantee": { "object": "guarantee", "kind": "imprint", "state": "refunded", "origin": "online_booking", "amount": 5000, "currency": "EUR", "charged_amount": 5000, "refunded_amount": 2500, "card": { "brand": "visa", "last4": "4242" }, "cancel_deadline_at": "2026-07-17T20:00:00+02:00", "expires_at": null, "consented_at": "2026-07-11T11:12:04+02:00", "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T22:31:00+02:00" }, "metadata": {}, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T22:31:00+02:00" } }}reservation.guarantee_released
Section titled “reservation.guarantee_released”The guarantee ended without a charge — the guest honoured the booking, or the
restaurant released the hold. Nothing was taken; charged_amount is 0.
{ "id": "evt_O4pQ5rS6tU7vW8xY9zA0bC1d", "object": "event", "type": "reservation.guarantee_released", "created": "2026-07-18T22:30:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_1M__exJ2ZQ7jKENZxz28djsH", "status": "completed", "source": "widget", "booking_channel": "website_widget", "party_size": 2, "service_date": "2026-07-18", "starts_at": "2026-07-18T20:00:00+02:00", "ends_at": "2026-07-18T22:00:00+02:00", "guest_notes": null, "contact_phone": "+33612345678", "internal_notes": null, "guest": "gst_e_I4WiTGNl0S1v7W2G6M6ygk", "company": null, "tables": [], "guarantee": { "object": "guarantee", "kind": "imprint", "state": "released", "origin": "online_booking", "amount": 5000, "currency": "EUR", "charged_amount": 0, "refunded_amount": 0, "card": { "brand": "visa", "last4": "4242" }, "cancel_deadline_at": "2026-07-17T20:00:00+02:00", "expires_at": null, "consented_at": "2026-07-11T11:12:04+02:00", "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T22:31:00+02:00" }, "metadata": {}, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T22:31:00+02:00" } }}reservation.guarantee_payment_failed
Section titled “reservation.guarantee_payment_failed”A charge attempt failed — an expired card, a decline, insufficient funds. The
guarantee stays chargeable until its window closes, so this is not necessarily
terminal: a later attempt may succeed and emit
reservation.guarantee_charged.
{ "id": "evt_P5qR6sT7uV8wX9yZ0aB1cD2e", "object": "event", "type": "reservation.guarantee_payment_failed", "created": "2026-07-18T20:31:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "reservation", "id": "resv_1M__exJ2ZQ7jKENZxz28djsH", "status": "no_show", "source": "widget", "booking_channel": "website_widget", "party_size": 2, "service_date": "2026-07-18", "starts_at": "2026-07-18T20:00:00+02:00", "ends_at": "2026-07-18T22:00:00+02:00", "guest_notes": null, "contact_phone": "+33612345678", "internal_notes": null, "guest": "gst_e_I4WiTGNl0S1v7W2G6M6ygk", "company": null, "tables": [], "guarantee": { "object": "guarantee", "kind": "imprint", "state": "charge_failed", "origin": "online_booking", "amount": 5000, "currency": "EUR", "charged_amount": 0, "refunded_amount": 0, "card": { "brand": "visa", "last4": "4242" }, "cancel_deadline_at": "2026-07-17T20:00:00+02:00", "expires_at": null, "consented_at": "2026-07-11T11:12:04+02:00", "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T22:31:00+02:00" }, "metadata": {}, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T22:31:00+02:00" } }}Guest events
Section titled “Guest events”The data.object of a guest event is a full guest, in the same shape as the
API reference returns; its notes are the restaurant staff’s
notes on the guest profile.
Guest events do not carry
previous_attributes — compare against your own stored copy if you need a diff.
A guest has a first name, a last name, or both: first_name or last_name can
be null, never both. A guest known by a single name keeps it in the field it
was recorded in.
guest.created
Section titled “guest.created”A guest record is created.
{ "id": "evt_A1bC2dE3fG4hI5jK6lM7nO8p", "object": "event", "type": "guest.created", "created": "2026-06-20T09:04:18Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "guest", "id": "gst_3Td9Lp0WqZ", "first_name": "Marie", "last_name": "Dupont", "phone": "+33612345678", "notes": null, "blacklisted": false, "blacklist_reason": null, "dietary_preferences": [], "allergies": [], "birthday": null, "anniversary": null, "vip": false, "language": "fr", "source": "widget", "marketing_email_consent": true, "marketing_email_consent_at": "2026-06-20T11:04:18+02:00", "marketing_sms_consent": false, "marketing_sms_consent_at": null, "visit_count": 0, "no_show_count": 0, "cancellation_count": 0, "first_visit_at": null, "last_visit_at": null, "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-20T11:04:18+02:00" } }}guest.updated
Section titled “guest.updated”A guest’s details change. (Visit statistics alone do not trigger this event — see Pagination.)
{ "id": "evt_B2cD3eF4gH5iJ6kL7mN8oP9q", "object": "event", "type": "guest.updated", "created": "2026-06-28T08:00:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "guest", "id": "gst_3Td9Lp0WqZ", "first_name": "Marie", "last_name": "Dupont", "phone": "+33612345678", "notes": null, "blacklisted": false, "blacklist_reason": null, "dietary_preferences": ["vegetarian"], "allergies": [], "birthday": null, "anniversary": null, "vip": true, "language": "fr", "source": "widget", "marketing_email_consent": true, "marketing_email_consent_at": "2026-06-20T11:04:18+02:00", "marketing_sms_consent": false, "marketing_sms_consent_at": null, "visit_count": 3, "no_show_count": 0, "cancellation_count": 0, "first_visit_at": "2026-06-21T21:00:00+02:00", "last_visit_at": "2026-06-27T20:00:00+02:00", "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-28T10:00:00+02:00" } }}guest.deleted
Section titled “guest.deleted”A guest is deleted (for example, a data-protection erasure request). The
data.object is a final snapshot of the full guest (the same shape as any
other guest payload); the guest is no longer retrievable from the API afterwards.
{ "id": "evt_C3dE4fG5hI6jK7lM8nO9pQ0r", "object": "event", "type": "guest.deleted", "created": "2026-06-29T07:00:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "guest", "id": "gst_3Td9Lp0WqZ", "first_name": "Marie", "last_name": "Dupont", "phone": "+33612345678", "notes": null, "blacklisted": false, "blacklist_reason": null, "dietary_preferences": ["vegetarian"], "allergies": [], "birthday": null, "anniversary": null, "vip": true, "language": "fr", "source": "widget", "marketing_email_consent": true, "marketing_email_consent_at": "2026-06-20T11:04:18+02:00", "marketing_sms_consent": false, "marketing_sms_consent_at": null, "visit_count": 3, "no_show_count": 0, "cancellation_count": 0, "first_visit_at": "2026-06-21T21:00:00+02:00", "last_visit_at": "2026-06-27T20:00:00+02:00", "created_at": "2026-06-20T11:04:18+02:00", "updated_at": "2026-06-28T10:00:00+02:00" } }}guest.merged
Section titled “guest.merged”Two guest profiles were merged — usually because the same person was recorded
twice. The data.object is the surviving guest.
data.previous_attributes is not used here. The absorbed profile’s id is not in
the payload: fetch the survivor if you need the merged state, and note that
the absorbed id keeps resolving — GET /v1/guests/{absorbed_id} returns the
survivor rather than a 404, so references you already hold stay valid.
{ "id": "evt_J9kL0mN1oP2qR3sT4uV5wX6y", "object": "event", "type": "guest.merged", "created": "2026-07-02T14:05:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "guest", "id": "gst_e_I4WiTGNl0S1v7W2G6M6ygk", "first_name": "Marie", "last_name": "Dupont", "phone": "+33612345678", "notes": null, "blacklisted": false, "blacklist_reason": null, "dietary_preferences": [], "allergies": [], "birthday": null, "anniversary": null, "vip": false, "language": "fr", "source": "manual", "marketing_email_consent": false, "marketing_email_consent_at": null, "marketing_sms_consent": false, "marketing_sms_consent_at": null, "visit_count": 4, "no_show_count": 0, "cancellation_count": 0, "first_visit_at": "2026-05-02T20:00:00+02:00", "last_visit_at": "2026-06-27T20:00:00+02:00", "created_at": "2026-05-01T10:00:00+02:00", "updated_at": "2026-07-02T16:05:00+02:00" } }}guest.unmerged
Section titled “guest.unmerged”A merge was undone and the absorbed profile restored as a separate guest. The
data.object is the restored guest — the one that had been absorbed.
After this, the restored id resolves to itself again rather than to the survivor.
If you cached the mapping from guest.merged, drop it.
{ "id": "evt_K0lM1nO2pQ3rS4tU5vW6xY7z", "object": "event", "type": "guest.unmerged", "created": "2026-07-03T09:30:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "guest", "id": "gst_FD7tTyOA9c-ZpPpeF0XMhkZI", "first_name": "Marie", "last_name": "Dupond", "phone": null, "notes": null, "blacklisted": false, "blacklist_reason": null, "dietary_preferences": [], "allergies": [], "birthday": null, "anniversary": null, "vip": false, "language": "fr", "source": "widget", "marketing_email_consent": false, "marketing_email_consent_at": null, "marketing_sms_consent": false, "marketing_sms_consent_at": null, "visit_count": 1, "no_show_count": 0, "cancellation_count": 0, "first_visit_at": "2026-06-01T20:00:00+02:00", "last_visit_at": "2026-06-01T20:00:00+02:00", "created_at": "2026-06-01T18:00:00+02:00", "updated_at": "2026-07-03T11:30:00+02:00" } }}Feedback events
Section titled “Feedback events”feedback.received
Section titled “feedback.received”A guest submits feedback after a visit. The data.object is an embedded
feedback value-object (it is not a retrievable API resource) carrying the
ratings and comment, plus bare-id references to the guest and the reservation it
relates to.
{ "id": "evt_D4eF5gH6iJ7kL8mN9oP0qR1s", "object": "event", "type": "feedback.received", "created": "2026-06-28T11:20:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "feedback", "id": "fb_7hn2qp", "overall_rating": 5, "food_rating": 5, "service_rating": 4, "comment": "Lovely evening, great service.", "responded_at": "2026-06-28T13:20:00+02:00", "guest": "gst_3Td9Lp0WqZ", "reservation": "resv_8xKQ2m4Vd0pErJ7sN1aZ9bQ" } }}The overall_rating, food_rating, service_rating, and comment fields may
each be null when the guest did not provide them. Use this event to invite
guests to leave a review, route feedback to the right team, or sync ratings into
your own tools.
Waitlist events
Section titled “Waitlist events”A waitlist entry is a guest’s request to be told if a table frees up on a given service date. It is not a reservation and holds no table.
The data.object of every waitlist event is a full
waitlist entry, in the same shape the API returns.
The offer hold and awaiting_guest
Section titled “The offer hold and awaiting_guest”The reservation an open offer holds sits in the status awaiting_guest: a real
table, held for one guest until they answer. Its sibling awaiting_guarantee
holds a table while a card is being saved.
The two are not equally visible to you. A card hold on a booking you already
know about stays in your feed, and is what
reservation.guarantee_requested carries. An
awaiting_guest hold has no published
booking behind it, so it is filtered out
of every reservation entrypoint: the list, the updated_since feed, a fetch by
id, and the events feed. ?status=awaiting_guest is accepted and returns
nothing. A hold the guest declined, or that ran out of time, becomes cancelled
and stays hidden under that status too.
Read awaiting_guest as a status you will never be sent. It appears in the
reference enum because that enum lists every status a
reservation can take internally. It reaches you with a booking attached in one
place only: created_status on the reservation.created entry in the events
feed, once the guest has accepted.
The reservation field
Section titled “The reservation field”reservation is null until the entry becomes a booking you can retrieve.
An entry that is mid-offer has a reservation internally, but that row is not yet
a published booking — so the field stays null rather than handing you an id
that would 404. Once it is set, GET /v1/reservations/{id} resolves it.
A promotion emits two events
Section titled “A promotion emits two events”When an entry becomes a booking you receive both
waitlist_entry.promoted and the ordinary
reservation.created, whose source is waitlist.
That is deliberate: a consumer that only handles reservation.* stays complete
and sees the booking appear like any other, while a consumer that tracks the
queue learns which request it came from. If you handle both, deduplicate on the
reservation’s own id — the two events describe one booking.
waitlist_entry.created
Section titled “waitlist_entry.created”A guest joined the waitlist. status is active, and reservation is null —
nothing is held.
{ "id": "evt_D4eF5gH6iJ7kL8mN9oP0qR1s", "object": "event", "type": "waitlist_entry.created", "created": "2026-07-11T09:12:04Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "waitlist_entry", "id": "wl_Os1uJlHmul2SpNy_0Ht1V6kY", "status": "active", "joined_via": "widget", "service_date": "2026-07-18", "party_size": 2, "window_start": "19:00", "window_end": "21:00", "window_start_day_offset": 0, "window_end_day_offset": 0, "shift_name": "Dinner", "shift_start_time": "19:00", "shift_end_time": "22:30", "notes": null, "reservation": null, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-11T11:12:04+02:00", "guest": "gst_z7p7NMpeGZcmL52fP-erOktL" } }}waitlist_entry.promoted
Section titled “waitlist_entry.promoted”A table opened up and the entry became a reservation. status is promoted and
reservation carries the booking’s id.
{ "id": "evt_E5fG6hI7jK8lM9nO0pQ1rS2t", "object": "event", "type": "waitlist_entry.promoted", "created": "2026-07-16T16:40:22Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "waitlist_entry", "id": "wl_Os1uJlHmul2SpNy_0Ht1V6kY", "status": "promoted", "joined_via": "widget", "service_date": "2026-07-18", "party_size": 2, "window_start": "19:00", "window_end": "21:00", "window_start_day_offset": 0, "window_end_day_offset": 0, "shift_name": "Dinner", "shift_start_time": "19:00", "shift_end_time": "22:30", "notes": null, "reservation": "resv_1M__exJ2ZQ7jKENZxz28djsH", "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-16T18:40:22+02:00", "guest": "gst_z7p7NMpeGZcmL52fP-erOktL" } }}waitlist_entry.updated
Section titled “waitlist_entry.updated”The guest changed their own request — the arrival window, the party size, or both. The entry keeps its id and its place; only what it is asking for changes.
This is one call, not a withdraw followed by a rejoin: the guest never loses
their place, and there is no window in which the entry does not exist. So you
will not see a cancelled / created pair for an edit, and an entry’s id
stays stable across one.
{ "id": "evt_Q6rS7tU8vW9xY0zA1bC2dE3f", "object": "event", "type": "waitlist_entry.updated", "created": "2026-07-12T10:04:17Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "waitlist_entry", "id": "wl_Os1uJlHmul2SpNy_0Ht1V6kY", "status": "active", "joined_via": "widget", "service_date": "2026-07-18", "party_size": 4, "window_start": "19:30", "window_end": "22:00", "window_start_day_offset": 0, "window_end_day_offset": 0, "shift_name": "Dinner", "shift_start_time": "19:00", "shift_end_time": "22:30", "notes": null, "reservation": null, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-12T12:04:17+02:00" } }}waitlist_entry.cancelled
Section titled “waitlist_entry.cancelled”The entry was withdrawn — by the guest from their own link, or by the
restaurant. status is cancelled.
{ "id": "evt_F6gH7iJ8kL9mN0oP1qR2sT3u", "object": "event", "type": "waitlist_entry.cancelled", "created": "2026-07-14T08:03:41Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "waitlist_entry", "id": "wl_Os1uJlHmul2SpNy_0Ht1V6kY", "status": "cancelled", "joined_via": "widget", "service_date": "2026-07-18", "party_size": 2, "window_start": "19:00", "window_end": "21:00", "window_start_day_offset": 0, "window_end_day_offset": 0, "shift_name": "Dinner", "shift_start_time": "19:00", "shift_end_time": "22:30", "notes": null, "reservation": null, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-14T10:03:41+02:00", "guest": "gst_z7p7NMpeGZcmL52fP-erOktL" } }}waitlist_entry.expired
Section titled “waitlist_entry.expired”The entry ran out of time — the service passed without a table freeing up.
status is expired.
{ "id": "evt_G7hI8jK9lM0nO1pQ2rS3tU4v", "object": "event", "type": "waitlist_entry.expired", "created": "2026-07-18T21:30:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "waitlist_entry", "id": "wl_Os1uJlHmul2SpNy_0Ht1V6kY", "status": "expired", "joined_via": "widget", "service_date": "2026-07-18", "party_size": 2, "window_start": "19:00", "window_end": "21:00", "window_start_day_offset": 0, "window_end_day_offset": 0, "shift_name": "Dinner", "shift_start_time": "19:00", "shift_end_time": "22:30", "notes": null, "reservation": null, "created_at": "2026-07-11T11:12:04+02:00", "updated_at": "2026-07-18T23:30:00+02:00", "guest": "gst_z7p7NMpeGZcmL52fP-erOktL" } }}Hold events
Section titled “Hold events”A hold is a claim on a slot. It occupies a real table without being a
booking: it carries no guest, it does not appear in GET /v1/reservations, and
the table is off sale for as long as the hold lives. Holds are created with
POST /v1/holds and handed back with DELETE /v1/holds/{id}, or released by the
restaurant from its back office — see the API reference.
The data.object of every hold event is a hold, not a reservation. It says
"object": "hold" and carries its own, smaller set of fields. Branch on
data.object.object if one handler receives both.
A hold’s id shares its suffix with the reservation it becomes, so hold_AbC
converts into resv_AbC.
The two timestamps are different times
Section titled “The two timestamps are different times”starts_at and ends_at are the seating the hold covers. expires_at is
when the hold lapses. Occupancy needs both: the table is unavailable for the
seating window, but only until the hold expires.
expires_at is the server’s answer, not yours. A create may ask for a deadline
and the server clamps it — a basket hold to at most 30 minutes (10 when
none is asked for), a commitment
hold to at most 48 hours, both to at least 5 minutes — so read it back off
hold.created or the create response rather than assuming the
number you sent. It is null on every terminal hold, because a hold that is
over has no deadline left to honour.
A conversion fires two events
Section titled “A conversion fires two events”When a hold becomes a booking you receive both
hold.converted and
reservation.created — same party, same moment. Treat
hold.converted as the hold being released and count the covers off
reservation.created alone, or the party is counted twice.
Creating a hold emits no reservation.created at all, so a held table stays out
of the reservation feed until somebody books it.
hold.created
Section titled “hold.created”A slot was held. status is held, reservation is null, and expires_at
carries the deadline the server set.
{ "id": "evt_P3qR4sT5uV6wX7yZ8aB9cD0e", "object": "event", "type": "hold.created", "created": "2026-07-04T17:12:40Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "hold", "id": "hold_9pLr4Xm2Vd0", "kind": "basket", "status": "held", "party_size": 4, "service_date": "2026-07-11", "starts_at": "2026-07-11T20:00:00+02:00", "ends_at": "2026-07-11T22:00:00+02:00", "expires_at": "2026-07-04T19:22:40+02:00", "ended_at": null, "reservation": null, "tables": [ { "object": "table", "id": "tbl_QpZ2", "name": "12", "min_capacity": 2, "max_capacity": 6, "section": { "object": "section", "id": "sec_Lm8", "name": "Main room", "area_type": "indoor" } } ], "metadata": { "your_reference": "basket-8841" }, "created_at": "2026-07-04T19:12:40+02:00", "updated_at": "2026-07-04T19:12:40+02:00" } }}metadata is echoed back verbatim and is carried onto the reservation when the
hold converts. It is readable by every API key the restaurant has issued, not
only by the one that wrote it — put reconciliation ids there, not commercial
terms.
hold.converted
Section titled “hold.converted”The hold became the reservation named in reservation. status is converted.
Both expires_at and ended_at are null: a converted hold did not end, it
turned into a booking. kind still reports what the hold was.
{ "id": "evt_Q4rS5tU6vW7xY8zA9bC0dE1f", "object": "event", "type": "hold.converted", "created": "2026-07-04T17:16:05Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "hold", "id": "hold_9pLr4Xm2Vd0", "kind": "basket", "status": "converted", "party_size": 4, "service_date": "2026-07-11", "starts_at": "2026-07-11T20:00:00+02:00", "ends_at": "2026-07-11T22:00:00+02:00", "expires_at": null, "ended_at": null, "reservation": "resv_9pLr4Xm2Vd0", "tables": [ { "object": "table", "id": "tbl_QpZ2", "name": "12", "min_capacity": 2, "max_capacity": 6, "section": { "object": "section", "id": "sec_Lm8", "name": "Main room", "area_type": "indoor" } } ], "metadata": { "your_reference": "basket-8841" }, "created_at": "2026-07-04T19:12:40+02:00", "updated_at": "2026-07-04T19:16:05+02:00" } }}hold.released
Section titled “hold.released”The hold was handed back before its deadline, and its table is on sale again.
status is released and ended_at is the moment it was given back.
Two doors lead here: your own DELETE /v1/holds/{id}, and the restaurant
releasing the hold from its back office — typically to seat a walk-in at that
table. The payload is the same either way. If you receive a hold.released you
did not ask for, the restaurant took the table back: the hold can no longer be
converted (POST /v1/reservations { hold_id } answers 404), so tell the guest
the time is no longer held and book fresh if they still want it.
Releasing an already-released or already-lapsed hold writes nothing and fires nothing, so you receive this event once per hold.
{ "id": "evt_R5sT6uV7wX8yZ9aB0cD1eF2g", "object": "event", "type": "hold.released", "created": "2026-07-04T17:19:31Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "hold", "id": "hold_9pLr4Xm2Vd0", "kind": "basket", "status": "released", "party_size": 4, "service_date": "2026-07-11", "starts_at": "2026-07-11T20:00:00+02:00", "ends_at": "2026-07-11T22:00:00+02:00", "expires_at": null, "ended_at": "2026-07-04T19:19:31+02:00", "reservation": null, "tables": [ { "object": "table", "id": "tbl_QpZ2", "name": "12", "min_capacity": 2, "max_capacity": 6, "section": { "object": "section", "id": "sec_Lm8", "name": "Main room", "area_type": "indoor" } } ], "metadata": { "your_reference": "basket-8841" }, "created_at": "2026-07-04T19:12:40+02:00", "updated_at": "2026-07-04T19:19:31+02:00" } }}hold.expired
Section titled “hold.expired”The hold ran out of time and its table is on sale again. status is expired
and ended_at is the moment the deadline was finalised.
{ "id": "evt_S6tU7vW8xY9zA0bC1dE2fG3h", "object": "event", "type": "hold.expired", "created": "2026-07-04T17:25:00Z", "livemode": true, "api_version": "2026-06-27", "data": { "object": { "object": "hold", "id": "hold_4kNt7Yq1Wb3", "kind": "commitment", "status": "expired", "party_size": 2, "service_date": "2026-07-06", "starts_at": "2026-07-06T12:30:00+02:00", "ends_at": "2026-07-06T14:00:00+02:00", "expires_at": null, "ended_at": "2026-07-04T19:25:00+02:00", "reservation": null, "tables": [ { "object": "table", "id": "tbl_Rk71", "name": "3", "min_capacity": 2, "max_capacity": 4, "section": { "object": "section", "id": "sec_Lm8", "name": "Main room", "area_type": "indoor" } } ], "metadata": {}, "created_at": "2026-07-02T19:20:11+02:00", "updated_at": "2026-07-04T19:25:00+02:00" } }}