# Remote Webhooks

Remote (global employment, contractor management and payroll) sends API webhooks to the callback
URL you register with `POST /v1/webhook-callbacks`. Each delivery carries two headers,
`X-Remote-Timestamp` (Unix **milliseconds**) and `X-Remote-Signature` (a bare lowercase hex
HMAC-SHA256 digest), and a JSON body whose `event_type` field names the event.

## When to Use This Skill

* How do I receive Remote webhooks?
* How do I verify the `X-Remote-Signature` header from Remote?
* Why does my Remote webhook signature verification fail?
* Where do I get the Remote webhook signing key? (the `signing_key` returned when you create the callback)
* How do I handle `employment.onboarding.completed`, `timeoff.requested` or `payslip.released` events?

## Verification (core)

```javascript
const crypto = require('crypto');

// signature = hex HMAC-SHA256(secret, raw_body + ":" + X-Remote-Timestamp)
function verifyRemote(rawBody, headers, secret = process.env.REMOTE_WEBHOOK_SECRET) {
  const sig = headers['x-remote-signature'];
  const ts = headers['x-remote-timestamp'];
  if (!secret || typeof sig !== 'string' || typeof ts !== 'string' || !ts) return false;
  if (!/^[0-9a-fA-F]{64}$/.test(sig)) return false; // validate before decoding

  const message = Buffer.concat([rawBody, Buffer.from(':'), Buffer.from(ts, 'latin1')]);
  const expected = crypto.createHmac('sha256', Buffer.from(secret, 'utf8')).update(message).digest();
  const received = Buffer.from(sig.toLowerCase(), 'hex');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

```

* The key is the callback's `signing_key`, used as plain UTF-8 text (not hex- or base64-decoded).
* Sign the **raw body bytes**, then a literal `:`, then the timestamp header exactly as sent. Never
  parse and re-serialise the body first.
* Return `401` for a missing, malformed or wrong signature. Remote treats any 4xx or 5xx as
  **delivered** and does not retry it (only transport failures are retried), so there is no retry to wait for.
* **No timestamp window by default.** Hookdeck retries and replays resend the original timestamp, so a fixed
  window would reject legitimate deliveries. If you want one, make it opt-in with
  `REMOTE_WEBHOOK_TOLERANCE_SECONDS` (unset or `0` = off) and compare against milliseconds, as the examples do.
* Express: read the body with `express.raw({ type: '*/*' })`. Next.js: `await request.arrayBuffer()`.
  FastAPI: `await request.body()`.

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

## Event Types

Dispatch on the body's `event_type`. Remote documents 112 event types; these are the ones the examples handle.

| Event | Sent When |
| --- | --- |
| `employment.onboarding.completed` | An employee's onboarding is fully completed |
| `employment.onboarding_task.completed` | An employee completes an onboarding task |
| `timeoff.requested` | An employee submits a time off request |
| `timeoff.approved` | A time off request is approved |
| `payslip.released` | A payslip becomes available |
| `contract.termination_date_reached` | A contract reaches its termination date |

See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/remote-webhooks/references/overview.md) for more, and log unknown types rather than failing.

## Environment Variables

```bash
REMOTE_WEBHOOK_SECRET=your_signing_key_here        # signing_key from POST /v1/webhook-callbacks
# REMOTE_WEBHOOK_TOLERANCE_SECONDS=300             # optional, off by default

```

## Local Development

```bash
npx hookdeck-cli listen 3000 remote --path /webhooks/remote

```

No account required: the CLI creates a guest account on first run and gives you a tunnel and a web UI.
Register the printed URL as your callback URL.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/remote-webhooks/references/overview.md) - What Remote webhooks are, delivery behaviour, events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/remote-webhooks/references/setup.md) - Register a callback and get the signing key
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/remote-webhooks/references/verification.md) - Signature details, the published test vector, gotchas
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/remote-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests