Instead of having AirMason email employees and admins, you can receive every event as an HTTP POST to a URL you control and drive your own notifications. Webhooks are configured per organization.
Configure
PUT /organizations/{orgId}/webhook | Access token
{
"url": "https://hooks.acme.com/airmason",
"events": ["*"],
"disableAirMasonEmails": true
}{
"url": "https://hooks.acme.com/airmason",
"events": ["*"],
"disableAirMasonEmails": true,
"secret": "whsec_7Hq…",
"createdAt": "2026-09-15T14:00:00.000Z",
"updatedAt": "2026-09-15T14:00:00.000Z",
"secretRotatedAt": null,
"previousSecretValidUntil": null
}urlmust behttpsand must not point atlocalhostor a private network address (400 validation_error). Changing the URL also affects deliveries that are already queued, because the URL is read at send time.eventsis a list of event types, or["*"]for everything. At most 50 entries; unknown types are rejected;"*"cannot be combined with explicit types; duplicates are collapsed and the list is stored in a canonical order. A["*"]subscription receives new event types automatically as they are added, while an explicit list needs the new type added to it.disableAirMasonEmails: truesuppresses the AirMason-sent employee and admin emails that your subscribed events replace (see Replacing AirMason emails below): you become responsible for notifying people using the event payloads, which include thenotification.titleandnotification.bodyAirMason would have used.secretis returned once; use it to verify signatures. The response is201when a secret was minted (first configuration, or re-creation after aDELETE) and200when an existing webhook was updated in place, in which case the body contains nosecret.
GET /organizations/{orgId}/webhook | Access token
Returns the current configuration (never the secret), including previousSecretValidUntil, which is null once a rotation window has passed. Returns 404 when no webhook is configured.
DELETE /organizations/{orgId}/webhook | Access token
Disables the webhook and returns 204. Pending deliveries are marked failed with the error webhook_disabled. A later PUT re-creates the webhook with a new secret.
Secret rotation
POST /organizations/{orgId}/webhook/rotate-secret | Access token
{
"secret": "whsec_9Kp…",
"secretRotatedAt": "2026-09-20T10:00:00.000Z",
"previousSecretValidUntil": "2026-09-21T10:00:00.000Z"
}The previous secret stays valid for 24 hours. During that window every delivery is signed with both secrets: the X-AirMason-Signature header carries two comma-separated entries, v1=<new>,v1=<previous>. Split the header on ,, trim each entry and accept the request when any entry matches. Outside a rotation window the header is the single v1=<hex> shown below.
Delivery
Each event is delivered as a POST with a JSON body and these headers:
POST /airmason HTTP/1.1
Content-Type: application/json
User-Agent: AirMason-Webhooks/1.0
X-AirMason-Event: employee.signed
X-AirMason-Delivery-Id: dlv_01J8…
X-AirMason-Timestamp: 1758029400
X-AirMason-Signature: v1=5f3a…
The signature is HMAC-SHA256(secret, "<timestamp>.<raw body>"), hex-encoded. Reject requests whose signature doesn't match. For 24 hours after a secret rotation the header lists two signatures (see Secret rotation).
Delivery guarantees:
At-least-once. We consider an event delivered when your endpoint returns any
2xxwithin 10 seconds. Anything else is retried: a non-2xx status (including3xx, since redirects are not followed), a timeout or a connection error. Response bodies over 64 KB are truncated in the delivery log.Retries use exponential backoff over roughly 24 hours: 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, then 24 h after the original attempt. After the final attempt the delivery is marked failed and AirMason is alerted.
Idempotency. Retries reuse the same
X-AirMason-Delivery-Id. De-duplicate on it: you may occasionally receive the same delivery twice.Ordering is not guaranteed across events; use
occurredAtin the payload.Granularity. Employee-level events are sent one per employee: publishing a new version to 400 people produces 400
employee.document_publishedevents, and launching a handbook to 400 people produces 400employee.handbook_assignedevents, so each carries that person's own activation or handbook link.
Inspecting deliveries
GET /organizations/{orgId}/webhook/deliveries | Access token
Filters: status (pending | succeeded | failed), limit (1–500, default 25) and offset. Returns { data, pagination: { limit, offset, total } }; each item looks like:
{
"id": "dlv_01J8…",
"eventId": "evt_01J8…",
"eventType": "employee.signed",
"status": "pending",
"attemptCount": 2,
"lastAttemptAt": "2026-01-16T09:15:39.000Z",
"nextAttemptAt": "2026-01-16T09:20:39.000Z",
"lastResponseStatus": null,
"lastError": "connect ETIMEDOUT",
"isRedelivery": false,
"redeliveredFrom": null,
"createdAt": "2026-01-16T09:14:38.000Z",
"attempts": [
{ "at": "2026-01-16T09:14:38.000Z", "responseStatus": 503, "durationMs": 812 },
{ "at": "2026-01-16T09:15:39.000Z", "error": "connect ETIMEDOUT", "durationMs": 10004 }
]
}POST /organizations/{orgId}/webhook/deliveries/{deliveryId}/redeliver | Access token
Replays one delivery and returns 202 with the new delivery: same evt_ id, new dlv_ id and a fresh retry chain. Returns 400 while the original delivery is still pending and 404 if the webhook has been disabled.
Payload shape
{
"id": "evt_01J8…",
"type": "employee.signed",
"occurredAt": "2026-01-16T09:14:37.000Z",
"organization": { "id": "1f2a…", "externalId": "acme-eu" },
"notification": {
"title": "Sam Okafor signed Employee Handbook 2026",
"body": "Sam Okafor (sam.okafor@acme.com) signed version 1.1 of Employee Handbook 2026 on 16 Jan 2026."
},
"data": { … }
}notification.title and notification.body are the plain-text subject and body of the email AirMason would have sent for this event, so you can use them or replace them with your own copy. For employee.handbook_assigned and employee.document_published they are the actual launch, access or publish notice as configured by the organization's admin: the event is the email, minus the sending. data varies by event type.
Objects shared across events (all ids are strings and match the REST resources):
handbook—{ id, name, slug, status, isLaunched }, wherestatusisdraft,publishedorarchived.version—{ id, number, label, publishedAt }.numberis the raw stored value (110) andlabelthe display form ("1.1").employee—{ id, employeeId, email, firstName, lastName, name, source, sourceId }.
Event types
Document (handbook) events
Type | Sent when | Key |
| A handbook is created |
|
| A handbook is launched (made available to employees for the first time). A launch also produces one |
|
| A version is published. Because a launch is not a publish, a partner subscribing to |
|
Employee events (one per employee)
Type | Sent when | Key |
| An employee is created (via API, import or HRIS) |
|
| An employee is given access to a handbook (directly, via group, or on launch) |
|
| A version is published with employee notification on: one event per employee who would have received the "new version" email, deduplicated per version and employee so you see each notice once |
|
| The employee signs |
|
| The employee acknowledges |
|
| A signature/acknowledgement reminder is due (per the handbook's reminder schedule) |
|
Auto Policy Update events (per handbook)
Type | Sent when | Key |
| A new policy applicable to the organization's purchased regions becomes available for a handbook. Today this is emitted by the daily policy-update run (the morning after the policy is published), not at the moment of publishing. |
|
| Auto Policy Update applies an update to a handbook |
|
policy is { id, title, updateType, effectiveDate, summary, regions[] }, where updateType is NEW or UPDATE. regions[] uses the GET /regions shape { code, name, country: { code, name } }, with FEDERAL for federal policies. summary is the policy's plain-text description and is repeated at the top level of data.
Replacing AirMason emails
With disableAirMasonEmails: true, an email is suppressed only when all three conditions hold: the webhook is enabled, the organization is active, and the webhook is subscribed to the event that replaces that email. A webhook subscribed to employee.signed alone still gets welcome and reminder emails sent by AirMason.
AirMason email | Replaced by |
Employee welcome / activation |
|
Employee launch, access and auto-welcome notices |
|
Employee publish notice ("new version") |
|
Employee signed confirmation |
|
Employee signature reminder |
|
Admin daily "new policy updates" email |
|
Admin Auto Policy Update auto-apply digest |
|
An admin who belongs to several organizations still receives admin emails for the organizations that are not suppressed. Suppressed sends are recorded in AirMason with the status Suppressed, so support can see what was withheld.
Partner API documentation: