Agent skill

JazzHR Webhooks Skill

Receive and verify JazzHR candidate-export webhooks. Use when setting up a JazzHR custom integration endpoint, debugging X-JazzHR-Signature verification, or handling CANDIDATE-EXPORT and VERIFY events.

Install this skill

npx skills add hookdeck/webhook-skills --skill jazzhr-webhooks


When to Use This Skill

  • How do I receive JazzHR candidate export webhooks?
  • How do I verify the X-JazzHR-Signature header?
  • How do I respond to the JazzHR VERIFY setup event?
  • How do I handle CANDIDATE-EXPORT events?
  • Why is my JazzHR webhook signature verification failing?

Verification (core)

JazzHR signs the raw request body with HMAC-SHA256 and sends the bare lowercase hex digest in X-JazzHR-Signature. The key is the Secret Key you entered when adding the custom integration. Nothing else is signed and there is no timestamp.

Node:

const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret || !/^[0-9a-f]{64}$/i.test(signatureHeader)) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
  return crypto.timingSafeEqual(Buffer.from(signatureHeader, 'hex'), expected);
}

Python:

import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest((signature_header or "").lower().encode(), expected.encode())

The docs' worked example: secret secret-key, body {"data":"JazzHR Verify Event"} gives 4fdc7dcdcb02bca16c7ec4a15b9d6436de1f186e11ff155d0ab178b5aca4f30e.

Read the raw body regardless of content type (Express: express.raw({ type: '*/*' })). Return 401 for a bad signature, and 200 for the signed VERIFY event.

For complete handlers with route wiring, event dispatch, and tests, see:

Event Types

JazzHR identifies the event in the X-JazzHR-Event header, not in the body.

X-JazzHR-EventDescription
CANDIDATE-EXPORTA candidate was exported manually or by a workflow helper; body is { "candidate": { ... } } (HROpen Candidate)
VERIFYSigned POST sent when saving the integration; body is {"data":"JazzHR Verify Event"}. Only needs a 200

Important Headers

HeaderDescription
X-JazzHR-SignatureHMAC-SHA256 hex digest of the raw body
X-JazzHR-EventCANDIDATE-EXPORT or VERIFY
X-JazzHR-UUIDUnique ID for the request (use for idempotency)
X-JazzHR-Export-ContextOptional; the job the candidate was exported from
LinkURL of the candidate's profile in JazzHR

Environment Variables

JAZZHR_WEBHOOK_SECRET=your_integration_secret_key   # Secret Key set on the JazzHR custom integration

Local Development

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

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 6, 2026

View on GitHub →