Agent skill
Fountain Webhooks Skill
Receive and verify Fountain Hire webhooks. Use when setting up a Fountain (hourly-workforce hiring) webhook endpoint, verifying the X-OBIQ-SIGNATURE-V2 HMAC-SHA256 signature, debugging 401s, or handling applicant screening events (stage changes, background checks) and Post-Hire worker events.
Install this skill
npx skills add hookdeck/webhook-skills --skill fountain-webhooks
Fountain (fountain.com) is hiring software for hourly workforces. Fountain Hire sends a JSON POST webhook when an applicant moves through screening (stage transitions, Checkr/Onfido background-check status) and when a Post-Hire worker is activated, deactivated, or has documents uploaded or approved.
Every request carries an X-OBIQ-SIGNATURE-V2 header: the hex HMAC-SHA256 of the raw request body, keyed with your Fountain Hire private API token.
When to Use This Skill
- How do I receive Fountain webhooks?
- How do I verify the
X-OBIQ-SIGNATURE-V2signature on a Fountain webhook? - How do I handle Fountain applicant (screening) and worker (Post-Hire) webhook events?
- Why does my Fountain webhook signature verification fail (or return 401)?
Verification (core)
const crypto = require('crypto');
function verifyFountainSignature(rawBody, signatureHeader, secret = process.env.FOUNTAIN_WEBHOOK_SECRET) {
if (!secret || !Buffer.isBuffer(rawBody)) return false;
// Exactly 64 hex characters: Buffer.from(x, 'hex') silently drops bad characters.
if (typeof signatureHeader !== 'string' || !/^[0-9a-fA-F]{64}$/.test(signatureHeader)) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest(); // key = UTF-8 token
return crypto.timingSafeEqual(Buffer.from(signatureHeader, 'hex'), expected);
}
- Header:
X-OBIQ-SIGNATURE-V2: <hex>. The bare hex digest, no prefix; compare the whole value. - Signed message: the raw body bytes only. No timestamp or id is signed, so there is no replay window to check.
- Key: your Fountain Hire private API token as UTF-8 bytes, used as-is (not hex- or base64-decoded). Rotating the API token rotates the webhook key.
- Python:
hmac.compare_on bytes afterdigest(bytes.fromhex(sig), hmac.new(secret.encode(), raw, hashlib.sha256).digest()) re.fullmatch(r"[0-9a-fA-F]{64}", sig); never comparestr. - Missing, malformed or mismatched signature: return
401. - Read the body raw (
express.raw({ type: '*/*' }),await request.arrayBuffer(),await request.body()) and parse JSON only after the check.
Paste your Fountain Hire private API token. Only the default body signing is supported: accounts switched to applicant ID signing fail verification.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Fountain has no event-name field or header. Dispatch on the shape of the payload:
| Event | Payload shape | Sent When |
|---|---|---|
applicant | { "applicant": { id, name, email, stage, funnel, background_ | An applicant changes stage, or a background check (Checkr/Onfido) changes status |
worker | Flat worker object { id, name, email, is_ | A Post-Hire worker is activated or deactivated, or documents are uploaded or approved |
Return 200 quickly and process slow work asynchronously: Fountain retries a failed request twice, and a webhook that fails more than 10 times in 24 hours is disabled until you fix and re-enable it.
Environment Variables
FOUNTAIN_WEBHOOK_SECRET=your_fountain_hire_private_api_token
Local Development
Fountain can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 fountain --path /webhooks/fountain
Use the printed URL as the webhook URL in Fountain Hire (Company Settings -> Webhooks).
Resources
- references/overview.md - What Fountain webhooks are, events and payload shapes
- references/setup.md - Configure the webhook and find your API token
- references/verification.md - Signature details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests