HollyHR Developer Docs
  • HollyHR
  • Sign in
  • Manage API keys
  • Start Here
  • Core API
  • AI and MCP
  • API Reference
  • Recipes
  • Resources
RequestsPagination examplesEnvironments and testingWebhooksProvider readinessOpenAPI imports
Core API

Webhooks

Use webhooks when your system needs to react to HollyHR changes without polling. In the beta API, webhook events are emitted for changes made through the public API write surface: people, employment, pending time-off records, time-off categories, time-off settings, working-pattern setup, and org-unit setup.

UI-origin and domain-wide event emission is not part of the beta surface yet. For example, a change made by a user inside the HollyHR app does not emit a public API webhook just because the same event type exists for public API-origin writes.

Create an endpoint

Create webhooks through the API reference or with:

TerminalCode
curl -X POST "$HOLLYHR_API_BASE_URL/webhooks" \ -H "Authorization: Bearer $HOLLYHR_API_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: idem_create_webhook_001" \ --data '{ "name": "Production webhook", "url": "https://example.com/hollyhr/webhooks", "subscribed_events": ["person.created", "time_off.created"] }'

The API key must include webhooks:manage plus the read scopes required for the subscribed event payloads. For example, person.created requires people:read, and time_off.created requires time_off:read. Endpoint URLs must be public HTTPS URLs. Localhost, private IP ranges, local domains, URL credentials, fragments, and private DNS resolutions are rejected. Org-unit lifecycle events such as org_unit.created and org_unit.archived require org_units:read.

Create and rotate responses return the plaintext signing secret once. Store it securely. Later reads only expose secret_last_four, and idempotency replays return signing_secret: null.

Event payload

Each delivery uses a small wrapper:

Code
{ "id": "evt_...", "type": "person.created", "api_version": "v1", "occurred_at": "2026-06-18T09:00:00.000Z", "data": { "id": "per_...", "resource_type": "person" } }

Webhook payloads deliberately exclude compensation, bank details, tax or government identifiers, dates of birth, home addresses, demographics, document bytes, and free-text sensitive notes. Fetch elevated personal data through GET /people/{personId}/personal with people:personal:read when needed.

Event families

Beta webhook events are emitted only for successful public API writes. Each event carries a thin, versioned payload with the event id, type, occurrence time, resource id, and safe projection data. Fetch current state through REST when you need more context.

FamilyEventsRequired endpoint scopeFollow-up read route
Synthetic testwebhook.testwebhooks:manageNone; use the returned signed test payload.
People and employmentperson.created, person.updated, person.ended, person.reactivatedpeople:readGET /people/{personId}
Time off requeststime_off.created, time_off.updated, time_off.cancelledtime_off:readGET /time-off/{timeOffId}
Time-off configurationtime_off_category.created, time_off_category.updated, time_off_category.archivedreference:readGET /time-off/categories
Time-off settingstime_off_settings.updatedtime_off:readGET /time-off/settings
Organisation unitsorg_unit.created, org_unit.updated, org_unit.archived, org_unit.reactivatedorg_units:readGET /org-units/{orgUnitId}
Working patternsworking_pattern.created, working_pattern.updated, working_pattern.archivedworking_patterns:readGET /working-patterns/{workingPatternId}

Endpoint subscriptions are scope-checked when you create or update a webhook. For example, a webhook that subscribes to both person.updated and working_pattern.updated needs webhooks:manage, people:read, and working_patterns:read.

Verify signatures

HollyHR signs the raw JSON request body with HMAC-SHA256 over:

Code
<unix_timestamp>.<raw_json_body>

The signature is sent as:

Code
HollyHR-Webhook-Signature: v1=<current-digest>[,v1=<previous-digest>]

During the 24-hour secret rotation overlap, HollyHR includes signatures for the current secret and the still-valid previous secret.

Always verify:

  • HollyHR-Webhook-Signature
  • HollyHR-Webhook-Timestamp
  • the raw request body, before JSON parsing changes it
  • the event id, so duplicate deliveries are ignored safely

Headers

HeaderMeaning
HollyHR-Webhook-IdStable event id, for deduplication.
HollyHR-Webhook-Delivery-IdDelivery id for this endpoint/event pair.
HollyHR-Webhook-EventEvent type.
HollyHR-Webhook-TimestampUnix timestamp included in the signed input.
HollyHR-Webhook-SignatureHMAC signatures for current/previous secret.

Retries and duplicates

Deliveries are at-least-once. Your endpoint should return a 2xx response only after it has safely accepted the event. Non-2xx responses, timeouts, 408, 429, and 5xx responses are retried with bounded backoff. Non-retryable 4xx responses become failed deliveries.

Deduplicate by HollyHR-Webhook-Id or your own stored delivery id before performing side effects.

If an active endpoint accumulates 10 retained terminally failed deliveries, HollyHR automatically disables the endpoint and creates an in-app notification for tenant system admins. The notification includes the endpoint name, failure threshold, and latest sanitized failure summary, and links to API settings. It does not include payload bodies, response bodies, signatures, signing secrets, or raw delivery payload data.

Fix the receiver, inspect the health and delivery logs, then re-enable the webhook with PATCH /webhooks/{webhookId} before redelivering failed events. Automatic disablement is audited and does not delete delivery history.

Delivery logs

Check endpoint health when you need an operational summary before paging through delivery logs:

TerminalCode
curl "$HOLLYHR_API_BASE_URL/webhooks/{webhookId}/health" \ -H "Authorization: Bearer $HOLLYHR_API_TOKEN" \ -H "Accept: application/json"

The health response includes the endpoint status, derived health status, delivery counts by status, recent success/failure timestamps, retry timing, and the latest sanitized failure summary. failing means at least one retained delivery is terminally failed and needs inspection. recovering means delivery is still pending or scheduled for retry. unknown means no retained delivery history exists yet.

Use delivery logs when you need to debug failed or delayed webhook deliveries:

TerminalCode
curl "$HOLLYHR_API_BASE_URL/webhooks/{webhookId}/deliveries?status=retry_scheduled" \ -H "Authorization: Bearer $HOLLYHR_API_TOKEN" \ -H "Accept: application/json"

List responses include delivery status, attempt count, retry timing, last HTTP status, failure class, and sanitized failure message. Fetch one delivery to see ordered attempt history:

TerminalCode
curl "$HOLLYHR_API_BASE_URL/webhooks/{webhookId}/deliveries/{deliveryId}" \ -H "Authorization: Bearer $HOLLYHR_API_TOKEN" \ -H "Accept: application/json"

Delivery logs never return payload bodies, request bodies, response bodies, signatures, or signing secrets. The resource field is null unless the API key also has the read scope for the event resource, for example people:read for person.updated.

Failure classes are network, http_retryable, and http_non_retryable. Terminal delivered and failed delivery logs are retained for 30 days. Pending and retry_scheduled deliveries are not pruned by retention cleanup. Do not depend on indefinite delivery-log availability.

Redeliver a failed delivery

If your receiver was down or rejected a delivery, fix the receiver first, then queue the existing delivery for another attempt:

TerminalCode
curl -X POST "$HOLLYHR_API_BASE_URL/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver" \ -H "Authorization: Bearer $HOLLYHR_API_TOKEN" \ -H "Accept: application/json" \ -H "Idempotency-Key: idem_redeliver_webhook_001"

Redelivery is allowed only when the webhook is active and the delivery is failed or retry_scheduled. HollyHR keeps the same event and delivery ids, preserves prior attempt history, and resets the delivery for the next worker run. Continue deduplicating by HollyHR-Webhook-Id.

Test delivery

Use the test endpoint to queue a synthetic webhook.test event:

TerminalCode
curl -X POST "$HOLLYHR_API_BASE_URL/webhooks/{webhookId}/test" \ -H "Authorization: Bearer $HOLLYHR_API_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: idem_test_webhook_001" \ --data '{"event_type":"webhook.test"}'

The response includes the signed headers and payload shape so you can verify your receiver before relying on production events.

API reference

Use the Webhooks section in the API reference for the exact request and response schemas:

  • GET /webhooks
  • POST /webhooks
  • GET /webhooks/{webhookId}
  • PATCH /webhooks/{webhookId}
  • DELETE /webhooks/{webhookId}
  • POST /webhooks/{webhookId}/rotate-secret
  • POST /webhooks/{webhookId}/test
  • GET /webhooks/{webhookId}/health
  • GET /webhooks/{webhookId}/deliveries
  • GET /webhooks/{webhookId}/deliveries/{deliveryId}
  • POST /webhooks/{webhookId}/deliveries/{deliveryId}/redeliver
Last modified on June 23, 2026
Environments and testingProvider readiness
On this page
  • Create an endpoint
  • Event payload
  • Event families
  • Verify signatures
  • Headers
  • Retries and duplicates
  • Delivery logs
  • Redeliver a failed delivery
  • Test delivery
  • API reference
JSON