Skip to content

Developer docs

Webhooks.

Hours AI tells your other systems what happened. Paste a Slack or Teams URL for a message, or your own https endpoint for signed JSON.

1. Add a webhook

In Hours AI, open Settings → Webhooks, give it a name, paste the URL and tick the events you want (none ticked means all). Slack and Teams URLs are recognised and get a message card; anything else gets JSON. The signing secret is shown once.

Send test delivers a webhook.test event immediately and shows the response code, so you can confirm the connection before anything real happens.

2. Events

TypeWhen
schedule.publishedA schedule was published
timeoff.requestedAn employee requested time off
timeoff.approvedA time-off request was approved
swap.approvedA shift swap or pickup was approved
clock.inAn employee clocked in
clock.outAn employee clocked out
timesheet.approvedA week of timesheets was approved
payroll.exportedA payroll file was exported
sales.recordedSales were recorded through the ingest

Payloads carry ids, the location, and the fields that changed. They never include wages, pay rates or contact details; use the ids with your own records.

3. What you receive

POST https://your-endpoint.example/hooks/hours
Content-Type: application/json
X-Hours-Event: schedule.published
X-Hours-Delivery: 9d7e…            (unique per delivery)
X-Hours-Signature: t=1789070400,v1=3f9a…
{
  "id": "5b2c8f4e-1a0d-4d1e-9e0c-2a3f4b5c6d7e",
  "type": "schedule.published",
  "createdAt": "2026-09-10T20:00:00.000Z",
  "location": { "id": "6cd5105c-…", "name": "Harvest Table" },
  "data": {
    "scheduleId": "a1b2c3…",
    "weekStart": "2026-09-14T00:00:00.000Z",
    "shiftCount": 42
  }
}

Reply with any 2xx within 10 seconds. Anything else, or a timeout, is retried.

4. Verify the signature

The signature is an HMAC-SHA256 of <t>.<raw body> with your secret, hex-encoded, where t is the Unix time the delivery was signed. Compare in constant time and reject timestamps older than five minutes.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret, signatureHeader, rawBody) {
  const parts = Object.fromEntries(signatureHeader.split(",").map((kv) => kv.split("=")));
  const t = Number(parts.t);
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;           // 5 minutes
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex"), b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

5. Retries and pausing

  • A failed delivery retries after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, then is marked dead.
  • Five consecutive failures pause the webhook and show the last error in Settings. Resume it once the endpoint is back; any delivery can be resent from the log.
  • Deliveries are at-least-once. Use X-Hours-Delivery (or the envelope id, shared by every subscriber of one event) to ignore repeats.

Security

  • Only https URLs are accepted.
  • Secrets are stored encrypted and can be rotated in Settings.
  • Webhooks are per location and can only be managed by that location’s managers.

Sending sales to Hours AI instead? Connect any POS.