Agent skill

Breezy HR Webhooks Skill

Receive and verify Breezy HR webhooks (recruiting / ATS). Use when setting up a Breezy HR webhook handler, debugging X-Hook-Signature verification failures, or handling events like candidateAdded, candidateStatusUpdated, candidateDeleted and companyPositionAdded. The signature is a bare hex HMAC-SHA256 of the raw body keyed with the whole whsec_ secret; it is not Standard Webhooks.

Install this skill

npx skills add hookdeck/webhook-skills --skill breezy-hr-webhooks


Breezy HR is a recruiting / applicant tracking system. It POSTs a JSON event to your HTTPS endpoint when candidates, positions and team notes change. Webhooks are available on Pro plans and are managed through the Breezy API.

When to Use This Skill

  • How do I receive Breezy HR webhooks?
  • How do I verify the Breezy HR X-Hook-Signature header?
  • Why is my Breezy HR webhook signature verification failing?
  • How do I handle candidateAdded or candidateStatusUpdated events?
  • Do I strip the whsec_ prefix from the Breezy HR secret? (No.)

Signature Verification (core)

ItemValue
HeaderX-Hook-Signature (also sent: X-Breezy-Webhook-Version)
AlgorithmHMAC-SHA256, hex digest, no prefix
Signed contentThe raw request body
KeyThe whole whsec_... secret as a UTF-8 string

The secret only looks like a Standard Webhooks / Svix secret. Do not use the standardwebhooks or svix libraries, and do not strip or base64-decode the whsec_ prefix.

const crypto = require('crypto');

function verifyBreezySignature(rawBody, signature, secret) {
  if (!signature) return false;
  const expected = crypto
    .createHmac('sha256', secret) // whole whsec_ string, UTF-8
    .update(rawBody)              // raw bytes, not JSON.stringify(req.body)
    .digest('hex');
  const a = Buffer.from(signature.trim().toLowerCase());
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Breezy's own code samples re-serialize the parsed JSON, but their troubleshooting section says the signature covers the raw body. Use the raw body: with Express that means express.raw({ type: '*/*' }), so verification works whatever Content-Type arrives.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

Every payload is an envelope: { "_id", "type", "object", "creation_date" }.

Event (type)Triggered When
candidateAddedA candidate enters the system (applied, sourced, recruited, referred)
candidateStatusUpdatedA candidate moves to a different pipeline stage
candidateDeletedA candidate is deleted in the web app
companyPositionAddedA position is created
companyPositionUpdatedPosition details change
companyPositionDeletedA position is deleted
companyPositionStateUpdatedPosition state changes (draft, published, closed...)
companyNotePostedA team discussion note is posted
companyNoteUpdatedA team discussion note is edited
companyNoteDeletedA team discussion note is deleted

Deduplicate on the envelope _id; Breezy can occasionally deliver an event more than once. Respond with a 2xx within 30 seconds; failures are retried up to 10 times with exponential backoff.

Environment Variables

BREEZY_HR_WEBHOOK_SECRET=whsec_your_secret   # returned once when you create the endpoint

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 breezy-hr --path /webhooks/breezy-hr

Breezy only accepts public HTTPS URLs (no localhost), so use the tunnel URL when creating the webhook endpoint.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →