Webhooks
Push booking, invoice, lease and customer events to your other systems automatically, within seconds of them happening.
When You'd Use Webhooks
Webhooks connect Camper BMS to systems that live outside it - a CRM, a staff chat, a spreadsheet, or a no-code tool like Zapier (which can pass events on to hundreds of other apps without any code). You don't need a developer, and if you don't connect anything, nothing depends on this feature.
Accessing Webhooks
Navigate to Integrations > Webhooks from the main navigation.
Adding an Endpoint
Click to add an endpoint and enter the secure (HTTPS) web address that should receive the events.
Choose which events it should receive, or leave them all selected to receive everything.
Tick the acceptance checkbox to confirm you accept responsibility for data sent to this endpoint.
Copy the signing secret so your receiving system can verify incoming messages.
Click Send test to fire a sample event and confirm the other end is receiving it.
You can pause, resume or delete a connection at any time.
Events You Can Listen For
Bookings - created, reserved, pending, confirmed, cancelled, or changed (a new check-in, check-out or site)
Arrivals and departures - checked in, checked out
Invoices - issued, paid, refunded
Leases - created, activated, renewed, ended, or changed (a new date, site, access code or vehicle registration)
Customers - a new guest record created
What's in the Data
Each message includes the event name, when it happened, and which campground it relates to, plus the details for that event (the guest and booking for a booking event, the invoice for an invoice event, the lease and its tenants for a lease event).
Consent travels with the data. Every guest in a payload carries a marketing-consent flag so your receiving system can respect it.
The IDs are safe to share. Identifiers in the payload are public reference codes, never internal database IDs.
Arrival details travel with the booking. Every booking payload carries the check-in and check-out dates and times, your park's timezone, and the access code and vehicle registration when they are set.
Security and Reliability
HTTPS only - endpoints must be secure web addresses.
Every message is signed with HMAC-SHA256 using your endpoint's secret. Your receiving system can recompute the signature to confirm the message genuinely came from Camper BMS. The Webhooks page provides copy-ready PHP, Node and Python verification examples.
Automatic retries - if your endpoint doesn't respond, Camper BMS retries a couple of times over the next few minutes. Each connection shows its consecutive-failure count, and a persistently broken endpoint is automatically paused (and you're emailed) so it isn't hammered.
Destination checks - Camper BMS verifies the destination is a genuine external server, blocking attempts to point webhooks at internal addresses.
Delivery Log
Every delivery attempt is recorded - the event, where it went, whether it succeeded, the response, and the exact data sent. Each delivery has a Resend button to re-fire a payload if the other system was down. Delivery records are kept for 90 days, then automatically deleted.
Billing and Consent
Webhooks are included at no extra charge - not metered. Like the API, they're available on every account and pause automatically while an account is restricted (for example, an unpaid invoice).
Webhooks are not gated behind marketing consent - they're operator-directed data flows, so the consent decision belongs to your receiving system (which is why the consent flag travels in every payload).
Developer Detail
Every webhook is an HTTP POST to your endpoint with a JSON body. Payloads are built at the moment the event happens.
Delivery
| Headers | Content-Type: application/json, X-CamperBMS-Event (the event name), X-CamperBMS-Signature (see below). |
| Signature | Hex HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret from the Webhooks page. |
| Timeout | 10 seconds. Redirects are not followed. |
| Retries | Three attempts: immediately, then after 60 seconds, then after a further 5 minutes. |
| Auto-disable | After 100 consecutive failed attempts the endpoint is switched off and the park owner is emailed. Re-enable it from the Webhooks page. Events that happened while it was off are not replayed. |
| Restricted accounts | Delivery pauses while the account is restricted and resumes on its own. Events during the pause are not replayed. |
| Test sends | The Send test action posts a booking-shaped payload with "test": true and placeholder IDs. |
Verify the signature over the exact bytes received, comparing in constant time:
// PHP
$expected = hash_hmac('sha256', $rawBody, $secret);
$valid = hash_equals($expected, $request->header('X-CamperBMS-Signature'));
// Node
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.get('X-CamperBMS-Signature')));Events
Every payload starts with the envelope below, followed by the block named in the Payload column. To act only on paid bookings, use booking_confirmed. booking_created also fires for unpaid holds and drafts.
| Event | Fires when | Payload |
|---|---|---|
booking_created | Any booking is created, whatever its status. This includes drafts and reserved checkout holds that may expire. | booking |
booking_reserved | A booking is created as, or moves to, reserved. | booking |
booking_pending | A booking is created as, or moves to, confirmed_pending. | booking |
booking_confirmed | A booking is created as, or moves to, confirmed_paid. | booking |
booking_cancelled | A booking moves to cancelled. | booking |
booking_changed | The check-in date, check-out date or site changes without a status change. | booking |
checked_in | The guest is checked in, by staff, self check-in or the API. | booking |
checked_out | The guest is checked out. | booking |
invoice_issued | An invoice is created, whatever its status: rent, utilities and manual lease invoices, and booking invoices. | invoice |
invoice_paid | An invoice becomes paid. | invoice |
invoice_refunded | An invoice becomes refunded. | invoice |
lease_created | A lease is created. | lease |
lease_activated | A lease becomes active. | lease |
lease_renewed | A lease is renewed. | lease |
lease_ended | A lease ends. | lease |
lease_changed | The start date, end date, site, end-of-lease action, occupancy start, access code or vehicle registration changes without a status change. Not sent for a renewal, which is lease_renewed. | lease |
customer_created | A customer record is created. | customer |
Envelope
Present on every payload.
| Field | Type | Notes |
|---|---|---|
event | string | One of the events listed above. |
test | boolean | true only for a test send from the Webhooks page. |
occurred_at | string | ISO 8601 with offset, in UTC. |
campground.id | string | Public reference code, never a database ID. |
campground.name | string | |
campground.timezone | string | IANA zone, for example Australia/Sydney. Check-in and check-out times are local to this zone. |
Booking payload
Events: booking_created, booking_reserved, booking_pending, booking_confirmed, booking_cancelled, booking_changed, checked_in, checked_out.
{
"event": "booking_confirmed",
"test": false,
"occurred_at": "2026-09-09T04:09:39+00:00",
"campground": {
"id": "CMG9Q6MGG72J",
"name": "Equinox Foreshore Reserve",
"timezone": "Australia/Melbourne"
},
"guest": {
"id": "CT3K8VQ2PL",
"email": "guest@example.com",
"first_name": "Sam",
"last_name": "Taylor",
"marketing_consented": true
},
"booking": {
"id": "BKZ57Y950WQK",
"check_in": "2026-09-18",
"check_out": "2026-09-19",
"check_in_time": "15:00",
"check_out_time": "11:00",
"site_type": "Powered Site",
"guests": 3,
"children": 1,
"infants": 1,
"status": "confirmed_paid",
"access_code": "6173",
"vehicle_registration": "XYZ-123"
}
}| Field | Type | Notes |
|---|---|---|
guest | object or null | The booking's customer. |
guest.id | string | Public reference code. |
guest.email, guest.first_name, guest.last_name | string | |
guest.marketing_consented | boolean | Whether the guest has given marketing consent. Webhooks are not consent-gated, so honour this before any marketing use. |
booking.id | string | Public reference code. The same ID the REST API accepts. |
booking.check_in, booking.check_out | string | YYYY-MM-DD. |
booking.check_in_time, booking.check_out_time | string | HH:MM, 24-hour, local to campground.timezone. The booking's own time if one is set, otherwise the park default. |
booking.site_type | string or null | The campsite area name. |
booking.guests | integer | Adults and children on the booking. |
booking.children | integer | How many of guests are children. 0 when there are none. |
booking.infants | integer | Babies and toddlers on the booking, counted on top of guests. They stay free. 0 when there are none. |
booking.status | string | draft, reserved, confirmed_pending, confirmed_paid or cancelled. |
booking.access_code | string or null | The access code sent to the guest before arrival, if set. |
booking.vehicle_registration | string or null | The number plate, upper case with single spaces, if set. |
Invoice payload
Events: invoice_issued, invoice_paid, invoice_refunded.
{
"event": "invoice_paid",
"test": false,
"occurred_at": "2026-09-09T04:09:39+00:00",
"campground": { "id": "CMG9Q6MGG72J", "name": "Equinox Foreshore Reserve", "timezone": "Australia/Melbourne" },
"guest": {
"id": "CT3K8VQ2PL",
"email": "guest@example.com",
"first_name": "Sam",
"last_name": "Taylor",
"marketing_consented": true
},
"invoice": {
"id": "IN7H2M4QWX",
"amount": 248.00,
"currency": "AUD",
"status": "paid",
"booking_id": "BKZ57Y950WQK",
"lease_id": null
}
}| Field | Type | Notes |
|---|---|---|
guest | object or null | The invoice's customer, in the guest shape above. |
invoice.id | string | Public reference code. |
invoice.amount | number | Dollars, two decimal places. |
invoice.currency | string | AUD unless the invoice says otherwise. |
invoice.status | string | For invoice_issued, the status the invoice was created with, usually unpaid. paid or refunded for the other two. |
invoice.booking_id | string or null | The booking the invoice belongs to. |
invoice.lease_id | string or null | The lease the invoice belongs to. |
Lease payload
Events: lease_created, lease_activated, lease_renewed, lease_ended, lease_changed.
{
"event": "lease_activated",
"test": false,
"occurred_at": "2026-09-09T04:09:39+00:00",
"campground": { "id": "CMG9Q6MGG72J", "name": "Equinox Foreshore Reserve", "timezone": "Australia/Melbourne" },
"lease": {
"id": "LS4P9X1RQ7",
"start_date": "2026-07-01",
"end_date": "2027-06-30",
"status": "active",
"site_type": "Powered Site",
"end_of_lease_action": "renew_same_term",
"occupancy_started_at": "2019-03-15",
"access_code": "4321",
"vehicle_registration": "ABC 123"
},
"tenants": [
{
"id": "CT3K8VQ2PL",
"email": "tenant@example.com",
"first_name": "Sam",
"last_name": "Taylor",
"marketing_consented": false
}
]
}| Field | Type | Notes |
|---|---|---|
lease.id | string | Public reference code. |
lease.start_date, lease.end_date | string or null | YYYY-MM-DD. end_date is null for an open-ended lease. |
lease.status | string | draft, active, expired or cancelled. |
lease.site_type | string or null | The campsite area name. |
lease.end_of_lease_action | string | terminate, renew_same_term or month_to_month. |
lease.occupancy_started_at | string or null | YYYY-MM-DD. When the tenant first moved in, which can be earlier than start_date on a renewed or migrated lease. |
lease.access_code | string or null | The access code for the tenant, if set. |
lease.vehicle_registration | string or null | The number plate, upper case with single spaces, if set. |
tenants | array | Every customer on the lease, each in the guest shape. May be empty. |
Customer payload
Events: customer_created. The guest block is as described under the booking payload.
{
"event": "customer_created",
"test": false,
"occurred_at": "2026-09-09T04:09:39+00:00",
"campground": { "id": "CMG9Q6MGG72J", "name": "Equinox Foreshore Reserve", "timezone": "Australia/Melbourne" },
"guest": {
"id": "CT3K8VQ2PL",
"email": "guest@example.com",
"first_name": "Sam",
"last_name": "Taylor",
"marketing_consented": false
}
}Test payload
The Send test action posts a booking payload with "test": true, "id": "test" on the guest and the booking, dates seven and ten days ahead, the park's default check-in and check-out times, and null for access_code and vehicle_registration. Its fields are identical to a real booking payload.
Related articles
Still need a hand?
Our support team is happy to help.
Contact support