Entity Visibility Webhooks
1. Purpose
Two independent mechanisms can make an entity record start or stop appearing in a domain API’s output, without the record itself having been created or deleted in HR Core:
- An Authorization filter change the record starts or stops matching the filter that decides what a given client is allowed to see.
- A Historical days (
historyDays) boundary crossing — the record’s relevant date moves across the subscription’s retention window, so the record starts or stops counting as “closed.”
Previously, both cases were reported using the entity’s own Insert/Delete operations, which was misleading: a filter or retention-window change is not the same event as a record actually being created or deleted in HR Core, but consumers had no way to tell the two apart from the webhook alone.
The fix: Insert and Delete are now reserved strictly for the entity’s own lifecycle (a genuinely new record, or a genuinely deleted one). Visibility changes caused by authorization or historical-days boundaries are now reported as their own operations — Authorize/Unauthorize and Archive/Unarchive — on the entity’s existing <entity>_CHANGED event (e.g. EMPLOYMENT_CHANGED), giving customers clarity about why a record appeared or disappeared.
2. The four operations
| Operation | Fires when… | Cause |
|---|---|---|
Authorize |
The record starts matching the client’s authorization filter | Authorization |
Unauthorize |
The record stops matching the client’s authorization filter | Authorization |
Archive |
The record’s closing date moves outside the historyDays window (becomes “too old” to show) |
Historical days |
Unarchive |
The record’s closing date moves back inside the historyDays window |
Historical days |
All four are carried on the entity’s normal <entity>_CHANGED event — there is no separate event name for visibility changes.
3. What counts as “closed” — per entity
The historyDays field is the historical retention window, in days, that determines how long a closed record stays visible in that domain’s API output after it becomes closed. Any record whose closing date falls further in the past than currentDate − historyDays stops being returned by the API; a record whose closing date is still within that window continues to be returned normally.
Which underlying field(s) supply that closing date differs by entity, and for a few entities it’s a comparison of two fields, taking whichever is more restrictive (i.e. the one that would archive the record sooner):
| Entities | Closing date rule |
|---|---|
| Employment, EmploymentExtensions, EmploymentPlannedChanges, EmploymentWorkSchedule, CostAllocation, Person, PersonExtensions, SalaryDetails, WorkPattern | dischargeDate field only |
| Assignment, PersonTimelines, EmploymentTimelines, SalaryDetailsTimelines, PersonExtensionTimelines, EmploymentExtensionTimelines | The more restrictive of fields dischargeDate and endDate |
| LeaveEntitlements, LeaveEntitlementChanges, LeaveHours | The more restrictive of fields dischargeDate and leaveEntitlementYear |
| MaternityLeave, SickLeave | The more restrictive of fields dischargeDate and recoveryDate |
| RoleAssignment | endDate field only |
| EmploymentTypes, JobProfile, JobProfileExtensions, OrganizationUnit, OrganizationUnitExtensions, PayrollClients, Vacancy, Workflow | Not affected by historyDays field at all |
Example (Employment): today is 23/01/2024; the learning domain has configured historyDays: 90. Employments discharged before 25/10/2023 (e.g. 24/10/2023) are archived; employments discharged on or after 25/10/2023 (e.g. 26/10/2023) are shown.
4. The authorization filter fields
Per the Filter configuration, a filter statement is built from a Field, an Operand (In or NotIn), and a set of Values. The valid Field values are: EmploymentType, Classification, PayrollClientCode, PayrollInstitutionCode, companyCode, employmentTypeName.
This filtering is defined per entity’s own authorization node, not per domain in the sense of “which domain the client is on” — every domain can have authorization configured, it’s the entity’s authorization node that the filter statement is checked against. Three entities are explicitly called out as having no authorization node at all: Job Profile, Organization Unit, and Employment Type — a filter can never apply to these.
5. Interaction rules
- Insert/Delete take priority over Authorize. When a brand-new entity is created and happens to already match the authorization filter, the webhook sent is
Insert— notAuthorize.Authorizeis reserved for a filter change affecting a record that already existed. - An authorization-caused
Unauthorizesuppresses the accompanying field-levelUpdate. If an update happens to change both the entity’s own data and, at the same time, an authorization-relevant field (e.g. Employment’semploymentType) such that the record now fails the filter, only theUnauthorizeoperation is sent — noUpdatewebhook for the same field change, because the record is now unauthorized and the update itself is no longer the consumer’s concern. - A historical-days-caused
Archive/Unarchivedoes not suppress the accompanying field-levelUpdate. If the change is a discharge-date (or other closing-field) update that also happens to cross thehistoryDaysboundary, both theArchive/Unarchivewebhook and a separateUpdatewebhook (carrying the changed field) are sent. - No redundant visibility webhooks. If a field changes but the record’s visibility status doesn’t actually flip (it was already authorized and remains authorized, or already archived and remains archived), no
Authorize/Unauthorize/Archive/Unarchivewebhook is sent — only the ordinaryUpdatefor the field, if anything changed. - Natural aging still triggers
Archiveon its own. A record doesn’t need any field to change to become archived — if the system’s periodic (daily) evaluation finds a record has aged past thehistoryDayswindow purely due to the passage of time,Archivefires without any preceding data change.
6. Examples (Employment)
Natural aging into historyDays — no config or data change
historyDays is set to 30 days. An Employment discharged on March 1, 2025 crosses the boundary once today’s date reaches April 1, 2025 (31 days later). The daily evaluation detects this and sends EMPLOYMENT_CHANGED / Archive — the API stops showing the entity.
A dischargeDate change pushes a record out of the window
historyDays is 90 days. employment1’s discharge date is January 31, 2025 (60 days before today, April 1, 2025 — still inside the window). The discharge date is then changed to December 22, 2024 (100 days before today — now outside the window). Both of the following are sent:
EMPLOYMENT_CHANGED/ArchiveEMPLOYMENT_CHANGED/Update(carrying thedischargeDatechange)
The API stops showing the entity.
A dischargeDate change pulls a record back into the window
Same setup, reversed: the discharge date moves from December 22, 2024 (100 days before today, outside the window) to March 7, 2025 (25 days before today, inside the window). Both are sent:
EMPLOYMENT_CHANGED/UnarchiveEMPLOYMENT_CHANGED/Update(carrying thedischargeDatechange)
The API resumes showing the entity.
An employmentType change pushes a record out via the authorization filter
A filter is configured to show records where employmentType is ABC. employment1 has employmentType: ABC and is visible. employmentType is then changed to YAW, no longer matching the filter. Only this is sent:
EMPLOYMENT_CHANGED/Unauthorize
No Update webhook is sent for the employmentType change — per the suppression rule above. The API stops showing the entity.
An employmentType change pulls a record back in via the authorization filter
Same filter (employmentType = ABC). employment1 has employmentType: 123 and is not visible. employmentType is then changed to ABC, now matching. Both are sent:
EMPLOYMENT_CHANGED/AuthorizeEMPLOYMENT_CHANGED/Update(carrying theemploymentTypechange)
(An earlier draft of this scenario used a non-existent operation value here — the correct operation is Authorize.) The API resumes showing the entity.
No webhook when visibility doesn’t actually change
A filter allows employmentType values ABC or YAW. employment1 has employmentType: ABC and is visible; an Authorize webhook was already sent for it previously. employmentType is then changed to YAW — still matching the filter, so visibility never flips. Only this is sent:
EMPLOYMENT_CHANGED/Update(carrying theemploymentTypechange)
No redundant Authorize is sent, since the record was already authorized.
7. Scope note
This document illustrates the mechanism using Employment only. The set of entities affected by authorization filtering and by historyDays is broader — see the per-entity closing-date table in section 3 for the historical-days side. The authorization side applies per entity’s own authorization node (see section 4) and is not limited to Employment; Employment is used here purely as a representative, well-documented example.