Events and what each one carries
Every event an endpoint can subscribe to, when each one is sent, and the payload a delivery of it carries.
Updated 8 October 2026 About 4 minutes View as Markdown
An endpoint subscribes to the events it wants, by name, on the project's
webhooks screen. Each delivery's signed body names the event in event and
carries the payload below in data, beside the delivery's own id: Verify a
delivery shows the whole body. Every id is a public
id, the same one the API answers with, so a payload can be followed up with a
call.
The events
project |
|
|---|---|
project.created |
Sent as a project is created, which is before it can have an endpoint, so no endpoint receives it today. |
project.updated |
The project's name, timezone or currency changed. |
project.deleted |
Sent as a project is deleted, once its endpoints are already gone, so no endpoint receives it today. |
module |
|
|---|---|
module.enabled |
A part was switched on for the project. |
module.disabled |
A part was switched off for the project. |
page |
|
|---|---|
page.published |
A page went live, or a new version of it did. |
page.unpublished |
A page was taken off the website, and kept as a draft. |
page.deleted |
A page was deleted. |
booking |
|
|---|---|
booking.created |
A booking was made. It may still be waiting for payment, or for you. |
booking.confirmed |
A booking was confirmed: paid for, accepted by hand, or confirmed on its own. |
booking.cancelled |
A booking was cancelled, by the customer or by the business. |
booking.rescheduled |
A booking moved to another time. The payload says where it was. |
booking.expired |
A booking waiting for payment ran out of time, and its slot was given back. |
booking.completed |
An appointment took place: sent once the business's day it was on is over, unless it was marked as a no-show first. |
payment |
|
|---|---|
payment.succeeded |
Money arrived. |
payment.failed |
A payment was declined or abandoned. |
payment.refunded |
Part or all of a payment was given back. |
payment.expired |
A payment page ran out without being paid. |
message |
|
|---|---|
message.received |
A message from the website's contact form arrived in the inbox, or was moved there out of spam. Spam is never announced. |
What a payload carries
Every event about one subject carries the same fields in data, so one
handler reads them all. The values below are examples. Every time is an instant
in UTC: show it on the project's timezone.
Bookings
{
"booking_id": "8f0c2b41-5d3e-4a7b-9c11-2e6f8a9d0b7c",
"livemode": true,
"service_id": "6f1c2a9e-4b7d-4e0a-9c55-2d8e1f0b7a31",
"staff_id": "b3e04c1d-9a6f-4f2e-8d1b-5c7a0e9f3d24",
"customer_id": "0d9e7f61-2c4b-4a8e-b5f3-71a2c6d8e9f0",
"starts_at": "2026-10-06T07:00:00+00:00",
"ends_at": "2026-10-06T07:45:00+00:00",
"status": "confirmed",
"amount_due": 2000,
"currency": "CHF"
}
amount_due is in minor units: 2000 is CHF 20.00. livemode is false for a
booking a test key made, and its events reach only your test endpoints. A few
events add one or two fields of their own: booking.confirmed names who
confirmed it in confirmed_by when a person did, booking.cancelled says who
in cancelled_by, and booking.rescheduled adds previous_starts_at and
moved_by.
Payments
{
"payment_id": "3c7d9e2a-1f4b-4c6d-8e0a-5b9f2d7c4e18",
"livemode": true,
"subject_type": "bookings.booking",
"subject_id": "8f0c2b41-5d3e-4a7b-9c11-2e6f8a9d0b7c",
"status": "paid",
"amount": 2000,
"currency": "CHF",
"is_final": false,
"metadata": {
"booking_id": "8f0c2b41-5d3e-4a7b-9c11-2e6f8a9d0b7c"
}
}
subject_type and subject_id say what the money was for, here a booking. A
payment opened on its own, with POST /v1/checkouts, was for nothing but itself:
its subject_type is empty and its subject_id is null. metadata is the
references the checkout was given, the same ones the API returns on the payment,
so a handler can find its order without a second call.
livemode is false for a payment a test key asked for, which took no real money.
is_final is true once nothing more can happen to the payment, so a handler
can stop expecting more events about it.
Pages
{
"event": "page.published",
"project": "atelier-rosa",
"page": {
"id": "5a2e8c71-9d4f-4b3a-a6e0-1c7b9f2d8e45",
"slug": "home",
"locale": "en",
"title": "Home",
"status": "published",
"url_path": "/home"
},
"cache_version": 42,
"version": {
"number": 7,
"published_at": "2026-10-06T08:15:00+00:00"
}
}
cache_version rises on every publish, so a static site can rebuild when it
moves and not otherwise. version is only on page.published.
Messages
{
"message_id": "c41f8a62-7b3d-4e9a-8f15-2d6b0e9c3a77",
"livemode": true,
"received_at": "2026-10-06T07:12:00+00:00"
}
Which message, and nothing anybody wrote in it: read the name, the address and
the words with GET /v1/messages/{id} and a secret key. Spam is never
announced, and a message moved out of spam is announced then, once.
The project
{
"project_id": "7e4b1d93-6a2c-4f8e-9b05-3d1a7c9e2f64",
"slug": "atelier-rosa",
"name": "Atelier Rosa",
"organization_id": "2b8f6e1a-4d9c-4e7b-a3f0-9c5d1e7b2a86",
"timezone": "Europe/Zurich",
"currency": "CHF"
}
{
"project_id": "7e4b1d93-6a2c-4f8e-9b05-3d1a7c9e2f64",
"module": "bookings"
}
Verifying a delivery
A payload is only worth acting on once its signature checks out. Verify a delivery shows how, with the SDK or without it.