Quick answer
Register an endpoint in Admin → Settings → Developer & API, choose the events
you want, and ASHR sends a signed POST when they happen. Verify the
X-Demystify-Signature header with the secret (shown once) so you can
trust the payload.

The payload
{ "event": "leave.created", "created": 1725448800, "data": { "id": "…", "employee_id": "…" } }
Headers: X-Demystify-Event: leave.created and
X-Demystify-Signature: t=1725448800,v1=<hex>.
Joiner and leaver events
employee.updated fires when an employee's status or department changes. Its
data is the employee id and changed_fields, the names of the fields that
changed, never their values. employee.exited fires once when an exit is
completed, with the employee id only. Neither payload carries a name or an
email: fetch GET /api/v1/employees/{id} with your key when you need the record.
{ "event": "employee.updated", "created": 1725448800, "data": { "id": "…", "changed_fields": ["department"] } }
Agent profile events
AI agents are workers too. worker.profile_changed fires when an agent is created
or changed, or switched on or off. Its data carries the worker id, its status,
change (created, updated, activated or deactivated), source, the names
of the changed fields and profile_path. It never carries a name, an email or a
value. Read the profile at profile_path with a key that has workers:read and
workers:write. worker.activated and worker.deactivated fire when an agent is
switched on or off.
{ "event": "worker.profile_changed", "created": 1725448800, "data": { "worker_id": "…", "status": "inactive", "change": "deactivated", "changed_fields": ["status"], "profile_path": "/api/v1/workers/…/profile" } }
Verify the signature
The signed string is `${t}.${rawBody}` and the signature is HMAC-SHA256 of
it with your endpoint secret. Recompute and compare in constant time; reject if
it doesn't match or t is more than a few minutes old.
import crypto from 'node:crypto'
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
const ok = crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(parts.v1, 'hex'))
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
return ok && fresh
}
Return a 2xx fast and process asynchronously; treat delivery as at-least-once, so make your handler idempotent.
Frequently asked questions
- Which events can I subscribe to?
- Events for leave requests, employees, documents, recruitment, payroll runs, attendance, helpdesk, expenses, workers and entitlements. The Developer & API settings list every event you can pick. Pick specific events or subscribe to everything.
- What do employee.updated and employee.exited contain?
- Only ids. employee.updated carries the employee id and the names of the fields that changed (status or department), never their values. employee.exited carries the employee id once, when an exit is completed. Neither carries a name or an email; read the employee through the API if you need more.
- What do the agent events contain?
- Only ids, states and field names. worker.profile_changed fires when an AI agent's profile changes, with the worker id, its status, what kind of change it was and the names of the changed fields. worker.activated and worker.deactivated fire when an agent is switched on or off. Read the full profile from the path the event names.
- How do I know a webhook really came from ASHR?
- Every request carries X-Demystify-Signature — an HMAC-SHA256 of "<timestamp>.<rawBody>" using your endpoint secret. Recompute it and compare; reject if it doesn't match or the timestamp is too old.
- What if my endpoint is down?
- Delivery is best-effort and each attempt is recorded. Build your handler to be idempotent (the same event may arrive more than once) and return a 2xx quickly.
Related articles
How do I use the ASHR.work API?
Create an API key with the scopes you need, send it as a Bearer token to /api/v1, and read or write your workspace data — with cursor pagination, idempotent writes and RFC 9457 errors.
How do I create API keys and use OAuth2?
Generate scoped API keys (shown once, hashed at rest), edit, rotate or revoke them; or register an OAuth2 client to exchange a client id + secret for short-lived access tokens with refresh.