Webhooks

Webhooks Overview

How BookYourPTO webhooks push events to your HTTPS endpoint, who can create and own endpoints, what a payload contains and the limits that apply.

Webhooks send an HTTPS request to your server when something happens in BookYourPTO, such as a leave request being approved, so your integration does not have to poll the API for changes.

Webhooks follow the same rules as the REST API. An endpoint only receives events about records its owner could read through the API, and a payload never contains more than the matching API endpoint returns.

Requirements

  • The same conditions as the API: a paid plan (Pro, Business or Enterprise), the API switched on, and a subscription in good standing. They are checked again before every delivery attempt, not only when the endpoint is created. If the API is switched off, the plan no longer includes it or the subscription lapses, nothing more is sent: queued deliveries are cancelled, not sent later.
  • Only Administrators and Executives can create, own and manage webhook endpoints. This is fixed: allowing other roles to create API tokens does not let them manage webhooks.

Endpoint owner

Every endpoint is owned by one person. By default that is the person who creates it. They can instead choose someone whose role is below their own: an Executive can create an endpoint owned by an Administrator, but an Administrator cannot create one owned by another Administrator or by an Executive.

Payloads are built with the owner's visibility, exactly as a token owned by that person would read them:

  • An event about a record the owner cannot see is not delivered to that endpoint. Nothing is sent: no empty payload, no id.
  • A private leave is redacted the same way it is for the owner in the API.
  • The owner must still be eligible when each delivery is attempted. If the owner is deactivated, offboarded or no longer an Administrator or Executive, queued deliveries are cancelled instead of sent.
  • When the owner is offboarded, or their personal data is deleted, every endpoint they own is turned off at the same moment. Reactivating the person later does not turn it back on: an Administrator or Executive gives the endpoint a new owner and resumes it.

Who can change an endpoint

Because an endpoint reads with its owner's visibility, changing where it sends or what it receives is held to the same rule as choosing its owner:

ActionWho may do it
See the endpoint and its delivery logAny Administrator or Executive
Pause or delete itAny Administrator or Executive
Resume it, send a test event, redeliverThe owner, or anyone whose role is the same as or above the owner's
Change its URL, events or owner, or rotate its signing secretThe owner, or anyone whose role is above the owner's

The full receiver URL is shown only to people who may change the endpoint; everyone else sees the host name, because a receiver URL often works as a credential.

Creating an endpoint

Endpoints are managed on the Webhooks tab under Settings > API. Choose Add endpoint, then fill in:

SettingRules
Endpoint URLMust start with https://, use port 443 or 8443, contain no user name or password, and name a public host rather than an IP address. At most 2048 characters. Every address the host name resolves to must be public; this is checked when you save and again before every delivery attempt.
EventsOne or more event types from the events reference, or Every event, which subscribes to every event the owner's role may receive, including event types added later.
DescriptionOptional, up to 200 characters.
OwnerYou, or someone whose role is below yours (see above).

When you save, the signing secret is shown once. It starts with whsec_. Copy it into your receiver's secret store; it cannot be displayed again. If it is lost, rotate it.

You can also manage endpoints through the API itself, which is how integration platforms subscribe with "REST hooks". See Managing endpoints through the API below.

Status

StatusShown asMeaning
activeActiveReceives events.
pausedPausedPaused by an Administrator or Executive. Pausing cancels every queued delivery.
disabledTurned offTurned off automatically, either after a long run of failed deliveries (see Retries & Failures) or because its owner was offboarded.

Resuming an endpoint replays nothing. Events raised while it was paused or turned off are not queued for it; failed deliveries can be redelivered from the delivery log.

Limits

  • Up to 10 webhook endpoints per organization.
  • Up to 50 event types per endpoint. Every event is a single entry and cannot be combined with individual events.

The payload

Every delivery is a POST with a JSON body:

{
  "id": "evt_01JABCDEFGHJKMNPQRSTVWXYZ",
  "type": "leave.approved",
  "api_version": "v1",
  "created_at": "2026-10-12T17:04:11.512Z",
  "organization_id": "cmorg0000000000000000000",
  "data": {
    "object": {
      "id": "cmlve0hmarsh2026080300",
      "status": "APPROVED",
      "...": "the other fields GET /leaves/{id} returns"
    }
  },
  "previous": { "status": "PENDING" }
}
FieldMeaning
idThe event id: evt_ followed by 26 characters. Unique per event and the same on every retry and redelivery. Use it to discard duplicates. It is also sent in the webhook-id header.
typeThe event type, for example leave.approved.
api_versionThe API version the payload follows, currently v1.
created_atWhen the event happened, as an ISO 8601 UTC instant.
organization_idThe organization the event belongs to.
data.objectThe record, in the same shape the API returns it, as the endpoint's owner sees it.
previousOn update and status-change events: the old values of the fields that changed, for example { "status": "PENDING" }. Left out when there is nothing to report.

The events reference has a complete example payload for every event type.

What a payload never contains

Payloads carry only the standard fields of a record, whatever scopes the owner holds. Personal details (date of birth, home address, emergency contact), government identifiers, compensation, timesheet pay figures, clock-in and clock-out locations and document files are never sent by webhook, and neither are secrets, tokens or storage paths. Fetch those through the API if your integration needs them and its token is allowed to read them.

Large records and deletions

A payload is limited to 64 KB. A record that would exceed it is sent as a thin event: data.object contains only its id and an object field naming the kind of record, for example { "id": "cmexp...", "object": "expense_report" }. Fetch the full record through the API.

department.deleted is always a thin event, because the department no longer exists.

Delivery guarantees

  • Delivery is at least once: the same event can arrive more than once. De-duplicate on id.
  • Delivery is not ordered: a later event can arrive before an earlier one. Use created_at, or fetch the current record from the API, when order matters.
  • The first attempt is normally made within about a second of the change being saved. Anything that attempt does not send is picked up by a job that runs every minute.

Testing an endpoint

Choose Send test event in the endpoint's menu to send a webhook.test event right away. The endpoint must be active. Open the endpoint's Delivery log to see the status code your server returned and how long it took.

A test event is signed exactly like every other event, so it is the quickest way to check your signature verification.

Managing endpoints through the API

Tokens owned by an Administrator or Executive can manage webhook endpoints with two scopes:

ScopeAllows
webhooks:readList endpoints, read one, read its delivery log, and list the event types (GET /webhook-events)
webhooks:writeCreate, update, pause, resume and delete endpoints, send a test event and redeliver

webhooks:write does not let a token read anything it could not read already. Subscribing an endpoint to an event, or changing an endpoint's URL, also needs the token to hold the read scope of every event the endpoint will receive (for Every event, ["*"], all of them); otherwise the answer is 403 SCOPE_MISSING. The read scope for each event is listed in the events reference and returned as required_scope by GET /webhook-events.

curl -X POST https://app.bookyourpto.com/api/v1/webhooks \
  -H "Authorization: Bearer $BOOKYOURPTO_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0f8e2a41-6c3d-4b7e-9a15-2d4c6e8f1b30" \
  -d '{
    "url": "https://hooks.example.com/hr/events",
    "events": ["leave.approved", "leave.cancelled"],
    "description": "Payroll system: leave changes"
  }'

The 201 response includes signing_secret, once. Through the API an endpoint's url is always returned masked to its host, for example https://hooks.example.com/…. Rotating the signing secret and redelivering every failed delivery at once are done in Settings > API.

Every operation is listed under Webhooks in the API Reference.

If your organization had a document-signing webhook set up before webhook endpoints were available, it has been copied into a paused endpoint subscribed to document.signed and document.declined, with the same receiver URL. Deliveries now use the signature scheme described in Verifying Signatures. Update the receiver, then resume the endpoint.

Next steps