apiwebhooksintegrationsengineering

Scheduling API and webhooks: getting shift data in and out

Most scheduling APIs are a second implementation of the product's rules, which is why they drift. Ours is the same code path the buttons use — here is what that buys you, plus the webhook contract in full.

7 min readWeekwright team

Here is a bug that ships in a lot of scheduling products. The app will not let you put someone on a shift that breaks a rest rule — it warns, it blocks, it makes you confirm. The API will, silently, because the API was written second, by a different person, against the database rather than against the rules.

Nobody notices until a schedule built by an integration produces a violation the UI would have caught, with no audit entry explaining who did it and no notification to the person whose Tuesday just moved.

One rule, four front doors

Every mutation in Weekwright is a tool — one function that owns one business rule, defined once. It is exposed four ways, and "exposed" is literal: the same function, not a reimplementation of it.

  1. The UI, when a manager clicks a button.
  2. The AI agent, when someone asks it to move a shift in chat.
  3. Internal callers — other tools, scheduled jobs, the test suite.
  4. HTTP: POST /api/rpc/{tool}, which is what your script calls.

That fourth door is a dispatcher, not an API layer. It resolves the tool by name and hands it to the same runner the button uses, so input validation, permission checks, plan and seat gates, the audit-log write and the realtime broadcast all happen because they were never bypassed. An integration that moves a shift produces the same audit row as a human moving it, and the manager watching the schedule in another tab sees it move.

The practical version: there is no list of "things the API can do" that differs from what the product does. There is one list. A tool that ships is callable the day it ships.

Keys have two independent ceilings

An integration key is not a password with the powers of whoever made it. Two bounds apply, and both have to pass:

  • Scope — an explicit allowlist of tool names. Default deny: a tool that is not named is a 403, never "probably fine". A key for a read-only export names the read tools and nothing else.
  • Role ceiling — the key acts as the member who created it, capped at a role you choose. The effective role is the lower of the two, so a key an owner created for a nightly export cannot be used to delete the organisation.

The consequence worth designing for: a key dies with its creator's membership. If the person who set up your payroll export leaves and is deactivated, the key stops working — and if they are demoted, the key is demoted with them. Offboarding a human offboards their automation, which is the failure mode most API-key systems leave to a checklist nobody runs.

Rate limits are two-tier and applied before authentication: 120 requests per minute per IP, 600 per minute per key. Limiting first means a flood of invalid keys costs a counter increment each rather than a database lookup each.

Webhooks: what we send, and how you verify it

Six events, deliberately coarse:

  • shift.changed, week.published, timeoff.decided, swap.changed, open_shift.changed, member.changed

A subscriber wants to know that a shift changed, not which of nine verbs occurred; the detail is in the payload, and a consumer that needs more re-reads through the API. The mapping from internal action to event is an explicit allowlist whose default is to send nothing — org settings, billing, audit reads and every internal read-model change stay in unless someone deliberately opts them out. "We accidentally streamed your payroll rates to a customer's automation" is not a recoverable mistake, so the default had to be silence.

Every delivery carries an X-Weekwright-Signature header shaped t=<unix>,v1=<hex>, where the MAC is HMAC-SHA256 over "{timestamp}.{body}" with your signing secret. Two rules for verifying it, and you need both: compare in constant time, and reject a timestamp outside a tolerance window — 5 minutes is what we recommend. A verifier that checks the MAC but ignores the timestamp can be replayed forever with a perfectly valid signature.

Failed deliveries retry with backoff up to five attempts, then stop. They stop rather than retrying forever because an endpoint that has been down for a day does not want the day's backlog when it returns, and a queue that never drains is an outage that never ends.

If you would rather not write code

The same outbound path drives the Slack integration — a schedule published in the app arrives in the channel your team already reads. If what you actually want is shifts on a phone's calendar rather than an event in a queue, the calendar feeds are the shorter path, and the integrations index lists what each one does and — on every page — what it does not.

The part we would push back on

Teams ask for an API earlier than they need one. The first question worth answering is whether the thing you want to automate is a schedule problem or a data problem. Pushing published weeks into a payroll system is a data problem and a good use of this. Rebuilding your own approval flow on top of the API is usually a schedule problem that the product should be solving, and if it does not, we would rather hear about it than have you maintain a second product.

Related: open shifts and shift bidding covers the coverage flow these events describe, and the webhooks page has the setup steps and the payload shapes.

Try Weekwright free for up to 20 employees.

No credit card required.