Agent skill
JobAdder Webhooks Skill
Receive and authenticate JobAdder recruitment CRM webhooks. Use when setting up a JobAdder webhook endpoint, checking the Authorization header configured on a webhook, debugging 401s, or handling events like candidate_status_changed, job_status_changed, jobapplication_status_changed and placement_approved.
Install this skill
npx skills add hookdeck/webhook-skills --skill jobadder-webhooks
JobAdder (recruitment CRM) sends an HTTP POST with a JSON body when an event occurs. Webhooks are created through the Webhook API (POST /webhooks, partner app with offline_access). Payloads are not signed. The only credential is the optional authorization value set on the webhook, which JobAdder sends back as the Authorization header.
When to Use This Skill
- How do I receive JobAdder webhooks?
- How do I verify the JobAdder
Authorizationheader on a webhook? - Does JobAdder sign webhook payloads? (No: shared
Authorizationvalue only) - Why does my JobAdder webhook endpoint return 401?
- How do I handle
candidate_status_changedorplacement_approvedevents?
Verification (core)
Set an authorization value when you create the webhook. Store it exactly as set, including any Bearer or Basic prefix, as JOBADDER_AUTHORIZATION. Compare the whole Authorization header to it. Never strip, parse or base64-decode it, even when it looks like Basic credentials:
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyJobAdderRequest(headers, expected = process.env.JOBADDER_AUTHORIZATION) {
if (!expected) return false; // never accept when unconfigured
const received = headers['authorization']; // full value, e.g. "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
return Boolean(received) && safeEqual(received, expected);
}
Return 401 when this fails, 400 for a body that isn't a JSON object, and 200 once handled. Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse it only after the check. Python and Next.js equivalents are in references/verification.md.
A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, dedupe on eventId, and rotate the credential (PUT /webhooks/{webhookId}) if it leaks.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Hookdeck Source Setup
Hookdeck's source type is JOBADDER (labelled "JobAdder"). Its setup note matches the above: "Paste the authorization value exactly as set in the JobAdder webhook, including any "Bearer " or "Basic " prefix."
Event Types
The body names the event in event. Payload envelope:
{ "apiVersion": 2, "event": "placement_approved", "eventId": "...", "eventDate": "2019-01-01T09:00:00Z", "eventUser": {}, "eventData": {} }
| Event | Triggered when |
|---|---|
candidate_status_changed | A candidate's status changes |
job_status_changed | A job's status changes |
jobapplication_ | A job application's status changes |
placement_approved | A placement is approved |
JobAdder has about 40 more (placement_status_changed, interview_scheduled, candidate_updated, jobad_posted, folder events and more); see references/overview.md. The docs say ordering is not guaranteed and duplicates may occur.
Environment Variables
JOBADDER_AUTHORIZATION=Basic dXNlcm5hbWU6cGFzc3dvcmQ= # full value as set on the webhook
Local Development
npx hookdeck-cli listen 3000 jobadder --path /webhooks/jobadder
No account required. Use the printed URL as the url of your JobAdder webhook.
Resources
- references/overview.md - Events, payload envelope, event filters
- references/setup.md - Creating a webhook with the Webhook API and the authorization value
- references/verification.md - Header check in Node, Python and Next.js, gotchas
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests