# Humaans Webhooks

Humaans is an HR information system (HRIS). Its webhooks tell you when people, time away, compensation, job roles, bank accounts, emergency contacts and equipment change, so you can sync payroll, IT provisioning and directories without polling. Webhooks are created by an Owner and delivered through Svix.

## When to Use This Skill

* How do I receive Humaans webhooks?
* How do I verify Humaans webhook signatures (`webhook-signature`, `whsec_` secret)?
* How do I handle `person.created`, `person.offboarded` or `timeAway.approved` events?
* Where do I get the Humaans webhook signing secret?
* Why is my Humaans webhook signature verification failing?

## Verification (core)

Humaans deliveries follow Standard Webhooks: `webhook-id`, `webhook-timestamp` (Unix seconds) and `webhook-signature` (space-delimited `v1,<base64>` entries). Svix can send the same values as `svix-id`, `svix-timestamp` and `svix-signature`, so accept either prefix. The signature is base64 HMAC-SHA256 over `{id}.{timestamp}.{raw body}`, keyed with the **base64-decoded** part of the `whsec_...` secret.

Use the `standardwebhooks` library (Node and Python). It throws on a bad signature or a timestamp more than 5 minutes off, and returns the parsed payload:

```javascript
const { Webhook } = require('standardwebhooks');   // Python: from standardwebhooks.webhooks import Webhook
const h = (name) => req.headers[`webhook-${name}`] ?? req.headers[`svix-${name}`];
const event = new Webhook(process.env.HUMAANS_WEBHOOK_SECRET).verify(rawBody, {
  'webhook-id': h('id'),
  'webhook-timestamp': h('timestamp'),
  'webhook-signature': h('signature'),
});

```

Verify against the **raw body** before parsing JSON (in Express use `express.raw({ type: '*/*' })` so any content type is read raw).

**Timestamp window and Hookdeck.** The `standardwebhooks` and `svix` libraries always enforce a fixed 5-minute timestamp window, and it can't be turned off. Hookdeck retries and replays resend the original `webhook-timestamp`, so the library rejects them once they are more than 5 minutes old. If you receive through Hookdeck and need late retries and replays to pass, use the manual verifier in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/humaans-webhooks/references/verification.md) (HMAC-SHA256 over `{id}.{timestamp}.{raw body}`, key = base64-decoded secret after `whsec_`, every space-separated `v1,<base64>` entry compared in constant time), where the window can be dropped or widened.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/humaans-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/humaans-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/humaans-webhooks/examples/fastapi/).

## Common Event Types

The body is an event wrapper (`id`, `seq`, `ts`, `action`, `subject`, `actor`, `entity`); dispatch on `action`.

| Event (`action`) | Sent when |
| --- | --- |
| `person.created` | A person is added |
| `person.updated` | A person's profile changes |
| `person.offboarded` | A person is offboarded (`person.reonboarded` when they return) |
| `timeAway.created` | A time away request is created (also `.updated`, `.deleted`, `.rejected`) |
| `timeAway.approved` | A time away request is approved |
| `compensation.updated` | A compensation record changes (also `.created`, `.deleted`) |

Other actions: `person.deleted`, `timeAwayAdjustment.*`, `bankAccount.*`, `jobRole.*`, `emergencyContact.*`, `equipment.*` (each `created`, `updated`, `deleted`). See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/humaans-webhooks/references/overview.md).

## Important Headers

| Header | Description |
| --- | --- |
| `webhook-id` (or `svix-id`) | Message ID; unchanged on retries, use it for idempotency |
| `webhook-timestamp` (or `svix-timestamp`) | Unix seconds of this delivery attempt |
| `webhook-signature` (or `svix-signature`) | `v1,<base64>` entries, space-delimited (several during secret rotation) |

## Environment Variables

```bash
HUMAANS_WEBHOOK_SECRET=whsec_xxxxx   # prodEndpointSignature of the webhook, e.g. whsec_vwbddlgH8lyKqHEDzn3EUpmk

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 humaans --path /webhooks/humaans

```

Use the printed URL as the webhook's production endpoint URL, then publish the webhook so Humaans starts delivering. Any 2xx response is a success; other responses are retried with exponential backoff.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/humaans-webhooks/references/overview.md) - What Humaans webhooks are, events, payload, delivery behaviour
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/humaans-webhooks/references/setup.md) - Create and publish a webhook in Humaans or via the API, get the signing secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/humaans-webhooks/references/verification.md) - Signature verification details, manual Node/Python verifiers, gotchas