> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ontime.hosai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Event catalog

> The nine webhook event types, when each fires, and the payload it carries.

Nine event types. This is the complete list — no others exist.

## The payload shape

Every event carries the same envelope:

```json theme={null}
{
  "event": "leave.approved",
  "data": {
    "recipientId": "3f1c9b2a-5d84-4e7a-9c11-2b6f8d0a4e73",
    "ref": { "kind": "leave", "id": "b81d3e40-2c67-4a19-8f53-7d0e6b4c1a92" },
    "title": "Leave approved"
  },
  "timestamp": "2026-07-25T09:14:22.481Z"
}
```

<ResponseField name="event" type="string">
  The event type. Matches the `X-Ontime-Event` header.
</ResponseField>

<ResponseField name="data.recipientId" type="uuid">
  The **employee the notification is for** — usually the person the event happened to, not the person who caused it.
</ResponseField>

<ResponseField name="data.ref" type="object | null">
  A reference to the affected record: `{ "kind": "...", "id": "..." }`. `null` for events with no navigable target.
</ResponseField>

<ResponseField name="data.title" type="string">
  A short human-readable label.
</ResponseField>

<ResponseField name="timestamp" type="string">
  ISO 8601, when the event was enqueued.
</ResponseField>

<Warning>
  **The payload does not contain the domain object.** There is no leave request, no payslip, no message body. Use `ref.id` to fetch what you need.

  It also does not carry the *actor* — who approved, who sent. Only the recipient.
</Warning>

## The events

<AccordionGroup>
  <Accordion title="leave.approved" icon="palmtree">
    A leave request reached its final approval. For a type requiring an HR step, this fires when HR approves, not when the manager does.

    `ref.kind` is `leave`; `ref.id` is the leave request. `recipientId` is the employee who requested the leave.
  </Accordion>

  <Accordion title="leave.rejected" icon="palmtree">
    A leave request was rejected, at either stage.
  </Accordion>

  <Accordion title="regularization.approved" icon="wrench">
    An attendance correction reached final approval — and therefore **the affected day record has already been rewritten**. If you cache attendance, this is the signal to re-read that date.

    `ref.kind` is `regularization`.
  </Accordion>

  <Accordion title="regularization.rejected" icon="wrench">
    An attendance correction was rejected. No day record changed.
  </Accordion>

  <Accordion title="overtime.approved" icon="timer">
    An overtime request was approved. The day record has been re-derived with the approved minutes, and if the day was a holiday or the employee's weekly off, comp-off has been credited.

    `ref.kind` is `overtime`.
  </Accordion>

  <Accordion title="overtime.rejected" icon="timer">
    An overtime request was rejected.
  </Accordion>

  <Accordion title="payslip.published" icon="receipt">
    A payroll cycle was disbursed and this employee's payslip exists. Fires once per employee in the cycle.

    <Note>
      This event is gated by the organization's **payslip** notification preference. With that toggle off, the payslips are still created but **no webhook fires**. If your integration depends on this event, confirm the toggle is on.
    </Note>
  </Accordion>

  <Accordion title="form16.available" icon="award">
    A Form 16 batch was distributed and this employee's document is in their vault. Gated by the **Form 16** notification preference.
  </Accordion>

  <Accordion title="message.received" icon="message-square">
    Somebody sent this employee a message in a manager-report thread. Gated by the **messages** preference.

    `ref.kind` is `message`.
  </Accordion>
</AccordionGroup>

## What does not fire a webhook

<Warning>
  OnTime raises more in-app notification types than it maps to webhooks. The following happen and are visible in the app but **never reach a webhook**:

  | Notification                 | Why it matters                                                                                                         |
  | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
  | **Swap approved / rejected** | Roster cells were rewritten and you will not be told.                                                                  |
  | **Comp-off earned**          | A leave balance changed.                                                                                               |
  | **Document verified**        | A document's status changed.                                                                                           |
  | **Punch reminder**           | Employee-facing only.                                                                                                  |
  | **Webhook failed**           | Deliberately excluded — the alarm that a delivery channel is broken must not try to announce itself over that channel. |

  If you need any of these, poll for them.
</Warning>

<Note>
  There is also **no event for a punch**. Attendance changes are not pushed; a `regularization.approved` is the closest signal that a past day changed, and nothing signals an ordinary punch. Poll `GET /api/public/v1/attendance` for a date.
</Note>

## Preference gating in general

<Warning>
  Webhook delivery rides the same path as in-app notifications, so **organization notification preferences silence webhooks too**. Turning off a category in Settings → Notifications stops the corresponding webhook from being enqueued at all.

  This is easy to miss when debugging "my webhook stopped firing". Check the preferences before the delivery log.
</Warning>
