Custom Features

Webhooks: receive events on your server

Lisa Wang

By Lisa Wang

November 11, 2026 · 7 min read

Webhooks send an event to your own server the moment something happens in OB, such as a new reservation, a cancellation, or a payment. This guide covers setup, the events available, what each request contains, how to verify it, and how retries work.

#webhooks#api#developer
On this page

When to use webhooks

Without webhooks, an integration has to keep asking the API whether anything has changed. With a webhook, OB calls your server as soon as the event happens, so your system stays up to date without polling and without using up the API rate limit.

Webhooks are part of the API custom feature, so that feature must be enabled. Each business has one webhook endpoint, and your developer needs a server with a public HTTPS address to receive the requests.

Set up the webhook

  1. Go to Admin → Custom Features → API → Webhooks.
  2. Enter your server's address in Webhook URL. It must start with https:// and be reachable from the public internet. Redirects are not followed.
  3. Leave Enabled on.
  4. Under Webhook events, switch on the events you want. A new endpoint starts with all of them on.
  5. Select Save.
  6. Under the address, select Copy below Signing secret and give the secret to your developer. It stays masked until you select the eye icon, and you can view or copy it again at any time, even before an address is saved.

To pause delivery later, turn Enabled off and save. The address and event choices are kept.

Each time the address is changed, OB sends it one test request and shows the result under the field: the response code in green for a 2xx answer, or a warning with the code or connection error. The test is a ping event with empty data; it is signed with your signing secret, or unsigned if the secret has never been shown and no address has been saved. A warning does not stop you saving. Your server should ignore the ping type.

Events you can receive

EventSent whenData
reservation.createdA booking is confirmed. A booking waiting for payment is sent once it is confirmed.Reservation
reservation.cancelledA booking is cancelled, by the customer or by staff, including when a whole session is cancelled.Reservation
reservation.attendanceThe attendance status of a booking changes, for example at check-in.Reservation
calendar.createdA session is added to the schedule.Calendar entry
calendar.updatedStaff edit a session, for example its time, room or size.Calendar entry
calendar.cancelledA session is cancelled.Calendar entry
user.createdA customer registers or is added by staff.User
user.updatedStaff edit a user, or a customer edits their own profile.User
user-pass.createdA customer holds a new pass. Sent once the payment is verified, so a bank transfer is announced after staff confirm it.User pass
user-pass.updatedStaff edit a pass a customer holds, for example its expiry date or quantity. Not sent when a booking uses the pass.User pass
payment.createdA payment is recorded, including a bank transfer that is still waiting to be verified.Payment with its items and total
payment.updatedStaff edit a payment, or the payment is verified.Payment with its items and total
class.created, class.updated, class.deletedStaff create, edit or delete a class.Class
pass.created, pass.updated, pass.deletedStaff create, edit or delete a pass.Pass

The Data is exactly the object the API returns for that record, for example the same object as GET /v1/reservations/{id} or GET /v1/payments/{id}, so one set of code can read both.

What each request contains

Every event is one POST request with a JSON body:

{
  "id": "2b4f1c0e-8a7d-4c1e-9f3a-6d5e4c3b2a10",
  "type": "reservation.created",
  "createdAt": "2026-11-10T09:30:00Z",
  "data": { ... }
}
  • id — unique for this delivery. Use it to ignore one you have already handled.
  • type — the event name.
  • createdAt — when the event happened, in UTC.
  • data — the record the event is about.

Three headers are included: X-OmceanBooking-Event (the event name), X-OmceanBooking-Delivery (the same value as id), and X-OmceanBooking-Signature.

The full field list for every object is in the API documentation.

Verify the signature

Anyone who learns your address could send it a request, so always check the signature before trusting one. The X-OmceanBooking-Signature header looks like t=1794300000,v1=5f2b…, where t is a timestamp and v1 is the signature.

  1. Take the timestamp, a full stop, and the raw request body exactly as received: t + "." + body.
  2. Calculate an HMAC-SHA256 of that text using your signing secret.
  3. Compare the result with v1. If they differ, reject the request.
  4. Reject requests whose timestamp is more than a few minutes old.

Use the raw body, not a re-encoded copy of the parsed JSON, or the signature will not match. A code sample is in the API documentation.

Respond, retries, and duplicates

Reply with any 2xx status within 10 seconds to confirm receipt. Do slow work after replying.

If your server replies with another status, times out, or cannot be reached, OB tries again after 1 minute, then 5 minutes, then 15 minutes. After those three retries the delivery is marked as failed and is not sent again.

  • Duplicates can happen. A retry may arrive after a request your server did process. Store the id and ignore one you have seen.
  • Order is not guaranteed. A retried event can arrive after a newer one. Use createdAt, or fetch the current record from the API, when order matters.
  • Each event is sent as it happens. Events are not batched.

Replace the signing secret

If the secret may have been exposed, go to Admin → Custom Features → API → Webhooks, select Regenerate below Signing secret, and confirm. The new secret is shown straight away. A secret that was only mislaid does not need replacing: select the eye icon to see it again.

The old secret stops working straight away. Requests sent after that are signed with the new one, so your server will reject them until it has been updated. Plan the change with your developer.

Troubleshooting

  • The address is rejected when saving. It must start with https://, its host name must resolve, and it must not point to a private or internal network.
  • Nothing arrives. Check that the API custom feature is enabled, that Enabled is on, and that the event you expect is switched on.
  • Check what was sent. Admin → Custom Features → API → Usage → Webhooks lists every event with its status, the response code your server returned, the number of attempts, how long the last attempt took, and any error.
  • A pass purchase did not send user-pass.created. A pass paid by bank transfer is announced only after staff verify the payment. payment.created is sent straight away.
  • The signature does not match. Use the raw request body, and make sure the server holds the latest secret after a regeneration.
  • Test tokens. The API sandbox used by test tokens never sends webhooks.

Was this article helpful?

Try the Booking System for 7 days

All Features Included

7 DAYS FREE$
  • ∞Unlimited bookings
  • ✨All AI features included
  • 🌐Your own branded booking website

No credit card needed

Arrow