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-Signatureheader? - How do I respond to the JazzHR
VERIFYsetup event? - How do I handle
CANDIDATE-EXPORTevents? - 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-Event | Description |
|---|---|
CANDIDATE-EXPORT | A candidate was exported manually or by a workflow helper; body is { "candidate": { ... } } (HROpen Candidate) |
VERIFY | Signed POST sent when saving the integration; body is {"data":"JazzHR Verify Event"}. Only needs a 200 |
Important Headers
| Header | Description |
|---|---|
X-JazzHR-Signature | HMAC-SHA256 hex digest of the raw body |
X-JazzHR-Event | CANDIDATE-EXPORT or VERIFY |
X-JazzHR-UUID | Unique ID for the request (use for idempotency) |
X-JazzHR-Export-Context | Optional; the job the candidate was exported from |
Link | URL 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
- references/overview.md - JazzHR candidate export concepts and payload
- references/setup.md - Adding a custom integration
- references/verification.md - Signature verification details