Agent skill
Factorial Webhooks Skill
Receive and authenticate Factorial HR webhooks. Use when setting up a Factorial webhook subscription and endpoint, verifying the x-factorial-wh-challenge header, debugging 401s, or handling subscription types like employees/employee/create_with_contract, timeoff/leave/create and ats/application/create.
Install this skill
npx skills add hookdeck/webhook-skills --skill factorial-webhooks
Factorial (HR software) sends a JSON webhook to your HTTPS endpoint when something happens: an employee is created, a leave request is approved, a shift is clocked in. Subscriptions are created through the API, one subscription_type per subscription. Webhooks are not signed.
Set a challenge on the webhook subscription. Without one, Factorial sends nothing to verify.
The optional challenge is a secret you define. Factorial returns it unchanged on every delivery in the x-factorial-wh-challenge header, and your endpoint authenticates the request by comparing the whole header value with the challenge you stored.
When to Use This Skill
- How do I receive Factorial webhooks?
- How do I verify the
x-factorial-wh-challengeheader? - Does Factorial sign webhook payloads? (No: use the subscription challenge)
- How do I handle
timeoff/leave/createoremployees/employee/create_deliveries?with_ contract - Why does my Factorial webhook endpoint return 401, or why do I receive no header at all?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyFactorialRequest(headers, challenge = process.env.FACTORIAL_WH_CHALLENGE) {
if (!challenge) return false; // never accept when unconfigured
const received = headers['x-factorial-wh-challenge']; // whole value, no prefix
return Boolean(received) && safeEqual(received, challenge);
}
- Header:
x-factorial-wh-challenge: <challenge>(name is case-insensitive). Sent only if the subscription has achallenge. - Python:
hmac.compare_on bytes; never passdigest(a.encode(), b.encode()) str(raises on non-ASCII). - Return
401on a missing header or mismatch,400for a body that isn't a JSON object,200once handled. - Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it.
A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS (Factorial requires it) and rotate the challenge if it leaks.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Subscription Types
The body is the affected resource's JSON with no event field: the subscription decides the type, in domain/resource/action form. Use one target_url per subscription (the examples add a ?type= hint) so your handler knows which resource it received.
| Subscription type | Sent When |
|---|---|
employees/employee/create_ | An employee is created together with a contract |
employees/employee/update | An employee record is modified |
employees/employee/terminate | An employee is marked as terminated |
contracts/contract_ | A contract version is created |
timeoff/leave/create | A leave request is submitted |
timeoff/leave/approve | A leave request is approved |
timeoff/leave/reject | A leave request is rejected |
attendance/shift/clock_ | An employee clocks in a shift |
ats/application/create | A job application is created |
ats/candidate/create | A candidate is created |
The full list is in the Webhooks section of Factorial's API reference.
Environment Variables
FACTORIAL_WH_CHALLENGE=your-subscription-challenge # the `challenge` you set on the subscription
Local Development
Factorial can't reach localhost and only delivers over HTTPS. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 factorial --path /webhooks/factorial
Use the printed URL as the subscription's target_url.
Resources
- references/overview.md - What Factorial webhooks are, subscription types, payload, retries
- references/setup.md - Create a subscription with a challenge through the API
- references/verification.md - Challenge check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests