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-V2 signature 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_digest(bytes.fromhex(sig), hmac.new(secret.encode(), raw, hashlib.sha256).digest()) on bytes after re.fullmatch(r"[0-9a-fA-F]{64}", sig); never compare str.
  • 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:

EventPayload shapeSent When
applicant{ "applicant": { id, name, email, stage, funnel, background_checks, ... } }An applicant changes stage, or a background check (Checkr/Onfido) changes status
workerFlat worker object { id, name, email, is_active, approved_at, data_collection_checks, ... }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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 12, 2026

View on GitHub →