Guest messaging and CRM
A guest messaging product or a CRM follows a guest through a stay: a booking arrives, a message goes out before arrival, another after departure, and the history stays in one place. This recipe shows what the API gives you for that.
Read the next section first, because it decides what you can build: by default the API tells you when a stay happens and what was booked, and it does not tell you who the guest is or how to reach them.
What the campground gets
Section titled “What the campground gets”- Messages that follow the stay on their own: before arrival, at check-in, after departure, and stopped when a booking is cancelled.
- A CRM that learns about new, changed and cancelled bookings as they happen.
Grondia sends some guest emails itself, such as the booking confirmation and the cancellation. Agree with the campground what your product sends on top of them, so a guest does not get the same news twice.
Personal data comes first
Section titled “Personal data comes first”Guest personal data is redacted by default on both lists. It is available on request, decided case by case with Felix (Credentials; the Personal data guide lists what each call returns).
| You read | By default | With the personal-data scope |
|---|---|---|
| Bookings | Dates, pitch or pitch type, number of guests, status, total and references. guest_first_name and guest_last_name come back empty and guest_email as "[redacted]" |
bookings:pii fills the two names and the email. A booking has no phone number and no postal address either way |
| Customers | id and date_joined. first_name and last_name come back empty, email as "[redacted]" and phone_number empty |
customers:pii fills the two names, the email and the phone number |
Neither personal-data scope can be created in the admin. Without one, your
product cannot reach a guest: there is no address to send to. Do not import
"[redacted]" as an email address, and do not treat an empty name as a missing
guest.
If you need contact data, write to Felix, felix@grondia.com, or to Siro, siro@grondia.com, say which campground and which fields, and why. Webhook bodies never carry personal data, with or without the scopes.
Scopes
Section titled “Scopes”| Scope | Opens | Who can create the key |
|---|---|---|
bookings:read |
GET bookings/, GET bookings/{id}/ |
The campground’s administrator |
customers:read |
GET customers/ |
The campground’s administrator |
webhooks:write |
The webhook endpoints, to receive events instead of polling | On request: ask Felix or Siro |
bookings:pii, customers:pii |
Lifts the redaction above | On request, case by case |
A key without webhooks:write still works: poll
GET bookings/?updated_since=<timestamp>, which returns bookings changed since
then, oldest change first (Syncing changes).
How the pieces fit
Section titled “How the pieces fit”Anna Müller books pitch A12 at Camping Beispielsee for 14 to 17 July.
Grondia: Anna Müller books pitch A12
Grondia to Your messaging or CRM product:
booking.booked. Ids, dates, pitch typeYour messaging or CRM product to Grondia:
GET bookings/{id}/ Grondia to Your messaging or CRM product: The booking, guest fields redacted unless
bookings:piiYour messaging or CRM product: Schedule the pre-arrival message for the day before
Grondia to Your messaging or CRM product:
booking.changed. Anna moves her arrivalYour messaging or CRM product to Grondia:
GET bookings/{id}/ Your messaging or CRM product: Reschedule
Grondia to Your messaging or CRM product:
guest.checked_inYour messaging or CRM product: Send the welcome message
Grondia to Your messaging or CRM product:
guest.checked_outYour messaging or CRM product: Send the thank-you message
The webhook says that something happened and names the booking. The booking itself, and who the guest is, you fetch.
The events
Section titled “The events”Register one endpoint with
POST webhooks/.
booking.booked, booking.changed and guest.created are opt-in: an endpoint
that leaves event_types empty does not receive them, so name them.
curl https://api.campone.ch/api/v1/public/webhooks/ \ -H "Authorization: Bearer ck_a1b2cXXXXXXXXXXXXXXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "url": "https://crm.example.com/hooks/grondia", "event_types": ["booking.booked", "booking.changed", "booking.cancelled", "guest.checked_in", "guest.checked_out"] }'The answer holds the signing secret once. Verify X-Grondia-Signature on every
delivery and answer 2xx within 10 seconds; the
webhook reference describes the headers, and a failed
delivery is retried up to five more times. The Webhooks
guide shows the signature check in code and the delivery log.
| Event | Opt-in | Use it to |
|---|---|---|
booking.booked |
Yes | A new booking, whatever its first status. Create the contact or deal, start the sequence |
booking.changed |
Yes | Dates, guests, price or status changed, or the booking was confirmed. Reschedule |
booking.cancelled |
No | Stop the sequence |
guest.checked_in |
No | The welcome message |
guest.checked_out |
No | The thank-you message or a review request |
guest.created |
Yes | A guest profile now exists. Carries an id and a reference only; read the section below before you rely on it |
The first five share one body, which holds dates and a pitch type but no guest and no pitch number:
{ "event": "booking.booked", "tenant": "beispielsee", "booking": { "id": 51277, "external_reference": "", "booking_reference": "7F3A9C21DE", "status": "confirmed", "check_in": "2026-07-14", "check_out": "2026-07-17", "site_type_code": "motorhome", "total": "105.00", "currency": "CHF", "updated_at": "2026-07-01T08:30:01.204117+00:00" }}The body is built when the delivery is made, so it shows the booking as it is
then. check_in and check_out are the campground’s local calendar dates:
schedule your messages in the campground’s time zone (Europe/Zurich for a Swiss
campground), not in yours.
Read the booking and the customer
Section titled “Read the booking and the customer”GET bookings/{id}/
answers the event. With the default scopes, Anna’s booking looks like this:
{ "id": 51277, "booking_reference": "7F3A9C21DE", "guest_first_name": "", "guest_last_name": "", "guest_email": "[redacted]", "site_id": 9012, "site_number": "A12", "site_category": "Motorhome pitch", "check_in_date": "2026-07-14", "check_out_date": "2026-07-17", "num_adults": 2, "num_youth": 0, "num_children": 0, "status": "confirmed", "total_price": "105.00", "created_at": "2026-07-01T08:30:00.918342Z", "updated_at": "2026-07-01T08:30:01.204117Z", "external_reference": "", "channel": ""}The customer list holds guests who have an account at the campground, newest first. With the default scopes Anna appears without her name and her contact details:
{ "id": 1042, "email": "[redacted]", "first_name": "", "last_name": "", "phone_number": "", "date_joined": "2026-06-20T14:03:22.417051Z"}Both lists come in pages of 50 (up to 200 with ?page_size=200).
Edge cases
Section titled “Edge cases”| Situation | What happens | What to do |
|---|---|---|
| You want to message a guest | The booking has no usable email by default | Agree bookings:pii with Felix, or get the contact data from the campground |
A booking is pending, abandoned or cancelled |
Only confirmed is firm. A pending booking may still be released, and an abandoned one never was firm (life of a booking) |
Read status before you send anything |
| The campground does not record check-in or check-out | No guest.checked_in or guest.checked_out |
Fall back to check_in and check_out |
| The same guest books twice | Two bookings, and a booking carries no guest id | With bookings:pii, match on email. Without it you cannot tell |
| A guest booked without an account | They are in bookings, not in the customer list | Treat bookings as your source for stays |
guest.created arrives |
See below | Do not rely on it to build a contact |
| You need a consent flag, or the guest’s language | Neither can be read through the API. When you create a booking you can send language (Create a booking) |
Ask the campground what it may send, and in which language |
| Your endpoint is down | The delivery is retried five more times, then given up | Catch up with GET bookings/?updated_since= (Syncing changes) |
guest.created
Section titled “guest.created”The event carries a guest id (a UUID), a guest_number such as GST-000123
and created_at, and nothing else. You cannot do much with it, for three
reasons:
- No endpoint of the API returns a guest by
idorguest_number, and no booking or customer response carries either. You cannot join the event to a booking or a customer. - The customer list is a different thing: guests with an account, with integer ids. A guest profile also exists for guests without one, such as walk-ins.
- A replay of a
guest.createddelivery is accepted (202) but sends nothing, because the delivery log cannot store a guest id. Do not plan on replay for it.
Use it as a signal that a new guest exists at the campground, or leave it out and derive your contacts from bookings. If the campground wants every new guest in a spreadsheet or another tool, the Zapier app fetches the full guest for its “New guest” trigger.
What you cannot do
Section titled “What you cannot do”The API reads guests and bookings. There is no endpoint to create or change a guest on its own, to add a note to a booking, or to send a message on Grondia’s behalf. Writing a guest or a note back to Grondia is possible through the Zapier app only.
Go-live checklist
Section titled “Go-live checklist”- You agreed with Felix or Siro which personal data you get, and from whom, before you built the sending.
- Nothing in your product treats
"[redacted]"as an email address or an empty name as a missing guest. booking.booked,booking.changedandguest.created(if you use it) are inevent_types, because they are opt-in.- Your handler fetches the booking, and sends only when its
statusisconfirmed. - A change or cancellation moves or stops a scheduled message.
- Messages go out in the campground’s time zone, and you agreed with the campground what Grondia sends itself.
- Your handlers are idempotent: key a booking on its
id. - A sweep on
updated_sincecatches what a downtime missed.
When the product works, ask for the review: Partner program.
Related
Section titled “Related”- Personal data: what each call returns, what a webhook carries and how to ask for access.
- Webhooks, Syncing changes and Errors: the guides behind the events and the catch-up.
- Zapier: the no-code way to write a guest or a note back.
- Channel managers: the recipe for products that create bookings.