Webhooks

What Easol sends to a webhook endpoint, how it signs requests and how it handles your response.

Easol sends each webhook as an HTTPS POST request to the endpoint URL you set in your Easol admin. To set up a webhook and build an endpoint, follow Receive webhooks. For the list of events, see Webhook events.

Easol webhooks follow the Standard Webhooks specification, so you can check them with any Standard Webhooks library.

Request headers

HeaderValue
Content-Typeapplication/json
User-AgentEasol-Webhooks/1.0
webhook-idThe ID of this delivery, such as msg_4f9c2a7b1e8d3c6a5b0f9e2d. It stays the same when Easol retries the delivery.
webhook-timestampWhen Easol made this attempt, as a Unix timestamp: the number of seconds since 00:00:00 UTC on 1 January 1970.
webhook-signaturev1, followed by the signature. See Signatures.

Header names aren't case-sensitive.

Each webhook gets its own webhook-id. If two of your webhooks subscribe to the same event, each receives a different ID for it.

Payload

The request body is a JSON object:

{
  "type": "booking.confirmed",
  "timestamp": "2026-10-21T09:30:00Z",
  "data": {
    "id": "bk_mR2nL5pW",
    "object": "booking"
  }
}
FieldDescription
typeThe event type, such as booking.confirmed.
timestampWhen the event happened, in ISO 8601 and UTC. Unlike the webhook-timestamp header, it stays the same when Easol retries a delivery.
data.idThe ID of the booking the event is about.
data.objectThe type of object. Always booking.

The payload doesn't include the booking's details. To get them, retrieve the booking with GET /bookings/{id}. The response describes the booking as it is when you ask, so it reflects changes made after the event. The payload has the same shape for every API version.

Booking events

Booking states

StateMeaning
pendingStarted, but not yet confirmed.
pending_paymentConfirmed, with a balance still owed.
completedConfirmed and paid in full, including free bookings and overpayments.
cancelledCancelled. The booking may or may not have been confirmed before cancellation.

“Confirmed” isn't a state. A booking is first confirmed when it moves from pending to pending_payment or completed. Easol sets booked_at at that point and sends booking.confirmed once. Cancelling a pending booking doesn't confirm it.

A later payment or price reduction can move a booking from pending_payment to completed, sending booking.fully_paid. An amendment that adds an unpaid balance can move it back to pending_payment. Paying that balance sends booking.fully_paid again, but not booking.confirmed.

Amended and updated

booking.amended means an edit to what was booked or what it costs has been applied. This includes items, packages, quantities, dates, reservations, prices, discounts and tax exemption. Easol sends one event per applied edit, even when the saved values are unchanged. Previews and edits that are rolled back don't send it.

booking.updated covers changes to a confirmed booking's own fields: state, money totals, billing address, purchase order number, VAT number and custom fields. It doesn't cover every change to the guests or line items included in the API response.

An action can send both events. Use booking.amended to hear about booking edits; use booking.updated to hear about changes to those booking fields. Subscribe to both if you need both kinds of change.

The following examples assume the booking has already been confirmed. The columns compare these two events only; an action can also send another event, such as booking.fully_paid or booking.cancelled.

Actionbooking.amendedbooking.updated
Apply an edit that changes a price, quantity, discount or tax exemptionYesYes, even if the total ends up the same
Apply an edit that only changes a reservation, without changing price or stateYesNo
Record a paymentNoYes
Change booking custom fields, billing address, purchase order number or VAT numberNoYes
Cancel the bookingNoYes
Change only guest detailsNoNo
Issue a refundNoNo; refunds don't send booking.updated yet

Before confirmation, Easol can send booking.amended, but it doesn't send booking.updated. One action can send booking.updated more than once. This is separate from delivery retries, which keep the same webhook-id.

Events can arrive out of order. Their payload identifies the booking; retrieve it through the API for its current state and details, rather than treating the event name as its current state.

Signatures

Easol signs every request with the webhook's signing secret, so you can check that the request came from Easol and that nobody changed it. Each webhook has its own secret. It starts with whsec_, followed by a base64-encoded key.

A Standard Webhooks library checks the signature for you. To check it yourself:

  1. Join the webhook-id header, the webhook-timestamp header and the raw request body with full stops, in that order: {webhook-id}.{webhook-timestamp}.{body}.
  2. Remove whsec_ from the start of the secret and base64-decode the rest to get the key.
  3. Use your language's HMAC-SHA256 function to sign the joined string with the key, and base64-encode the result.
  4. Compare your result with the value after v1, in the webhook-signature header. Use a constant-time comparison function, such as crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python. An ordinary string comparison can let an attacker work out the signature from how long your server takes to respond.
  5. Reject the request if webhook-timestamp is more than 5 minutes before or after your server's time. This stops someone from sending you an old request again.

Use the request body exactly as you received it. If you parse the JSON and convert it back to a string, the bytes can change and the signature won't match.

The webhook-signature header can hold several signatures, separated by spaces. Easol sends one, but accept the request if any of them matches.

Responses

Your responseWhat Easol does
Any 2xx statusRecords the delivery as successful. Easol ignores the response body.
410 GoneTurns off the webhook straight away and stops retrying.
Any other status, including a 3xx redirectRecords a failed attempt and retries. Easol doesn't follow redirects.
No response within the time limits, or a failed connectionRecords a failed attempt and retries.

Easol allows 5 seconds to look up your host and connect, and 30 seconds for the whole request.

Easol keeps the first 10 KB of each response body to show in the delivery log.

Retries

Easol makes up to 10 attempts for each event over roughly three days. After successive failures, the delays are 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 14 hours, 20 hours and 24 hours. Each delay includes up to 10% extra random time to spread out retries.

Internal Easol errors appear in the delivery history but do not consume this retry limit or count towards disabling your webhook. Easol retries those errors with backoff, so they can extend the delivery period.

Due retries are picked up once a minute, so the first retry usually arrives within about a minute. Queue load can delay any attempt further.

To ask Easol to wait longer, return a Retry-After header containing a number of seconds. Easol uses the longer of that value and its scheduled delay, capping the requested wait at 24 hours. Invalid values and HTTP dates are ignored.

Pending deliveries survive a lost background job or a restart. A delivery interrupted before its result is saved becomes eligible for retry after 10 minutes.

Each retry has the same webhook-id and body as the first attempt, with a new webhook-timestamp and signature.

If every attempt fails, Easol stops trying to deliver that event. You can't resend it from Easol. To find what you missed, use the API, as described in Receive webhooks.

Before each attempt, Easol checks that the webhook is still turned on and still listens for the event type. If it isn't, Easol skips the attempt and stops trying to deliver that event.

Order and duplicates

Easol doesn't guarantee the order of events. A retry can arrive after a later event, and two events about the same booking can arrive at the same time.

Your endpoint can receive the same delivery more than once. Store the webhook-id of each delivery you handle, and skip any delivery whose ID you've already seen.

Automatic turn-off

Easol turns a webhook off in either of these cases:

  • Your endpoint responds with 410 Gone.
  • Every attempt since the last successful delivery has failed, there have been at least 10 failed attempts, and the first of them was more than 3 days ago.

A webhook that Easol has turned off shows as Disabled in your Easol admin. Easol doesn't email you when this happens. Easol doesn't send events to a turned-off webhook, and doesn't send them later when you turn it back on.

Turning a webhook back on resets its count of failed attempts.

Endpoint URL rules

Easol only sends webhooks to endpoints on the public internet. An endpoint URL must:

  • start with https://
  • use a public domain name, not an IP address or a local name such as localhost or a .internal, .local or .test domain
  • not include a username or password
  • not point to an Easol domain, including a site address on an Easol domain
  • resolve only to public IP addresses

Each webhook in your company must use a different URL.

Easol checks the domain name again before each delivery. If the name no longer resolves, or resolves to an address that isn't public, the attempt fails and counts towards turning the webhook off.

Delivery log

Easol records every delivery attempt: the request body, your response status and body, and how long your endpoint took. You can see these on the webhook's Event deliveries tab for 3 months. Pending events and their attempts are kept until delivery finishes or retries are exhausted. Failures before Easol prepares a request are also recorded, with an error and no request body.

On this page