Agent skill

Recruitee Webhooks Skill

Receive and verify Recruitee webhooks. Use when setting up Recruitee webhook handlers, debugging X-Recruitee-Signature verification, or handling recruiting events like new_candidate, candidate_moved, candidate_assigned, and candidate_deleted.

Install this skill

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


When to Use This Skill

  • How do I receive Recruitee webhooks?
  • How do I verify the X-Recruitee-Signature header?
  • How do I handle new_candidate, candidate_moved, or candidate_assigned events?
  • How do I detect a disqualified candidate in a Recruitee webhook?
  • Why is my Recruitee webhook signature verification failing?

Verification (core)

Recruitee signs the raw request body with HMAC-SHA256 keyed on the webhook secret and sends the hex digest in X-Recruitee-Signature (no prefix, no timestamp). There is no official SDK, so verify manually: compute the HMAC over the raw body (before JSON parsing) and compare timing-safe.

The event name is in the body, not a header. Every payload is { id, attempt_count, created_at, event_type, event_subtype, payload }.

Node:

const crypto = require('crypto');

function verifyRecruiteeWebhook(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  // Compare hex strings as UTF-8 bytes; Buffer.from(x, 'hex') stops at the first non-hex char
  const received = Buffer.from(signatureHeader.trim().toLowerCase(), 'utf8');
  const wanted = Buffer.from(expected, 'utf8');
  return received.length === wanted.length && crypto.timingSafeEqual(received, wanted);
}

Python:

import hmac, hashlib

def verify_recruitee_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature_header.strip().lower().encode(), expected.encode())

Recruitee publishes a test vector: secret DuP4ej5yyJB5TIrIEI/dCtJN7sHj with a candidate_moved body gives 3b64e3049cb9e108fbb18a453e909cb4a32e3ae140ea01d84c2b5d316c19162f (see the tests in the examples).

Read the body as raw bytes regardless of content type (Express: express.raw({ type: '*/*' })), because any re-serialisation changes the digest.

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

Test Request

When you click Test or Verify and create, Recruitee POSTs {"test":true} and the endpoint must answer 200 for the webhook to be created. Handle this before event dispatch.

Common Event Types

event_type carries the event; event_subtype refines it.

event_typeevent_subtypeTriggered When
new_candidateemail, career_site, manual, importedA new candidate applies or is created
candidate_assignedemail, career_site, manual, importedA candidate is assigned to a job or talent pool
candidate_movedstage_changed, disqualified, requalifiedA candidate changes pipeline stage, is disqualified, or is requalified
candidate_deletedmanualA candidate is deleted
offer_publishedmanualA job is published
offer_unpublishedmanualA job is unpublished or archived
offer_updatedoffer_changed, status_changed, tags_changedA job is updated

There is no candidate_disqualified event type. A disqualification arrives as candidate_moved with event_subtype: "disqualified" and payload.details.disqualify_reason.

For the full event reference, see Recruitee webhook docs.

Important Headers

HeaderDescription
X-Recruitee-SignatureHex HMAC-SHA256 of the raw body, keyed with the webhook secret
User-Agent: Recruitee-WebhooksIdentifies Recruitee requests. Do not use for auth

Delivery Behaviour

  • Respond with exactly 200 within 10 seconds. Any other status is a failure.
  • Failures retry up to nine times (1 min, 3 min, 10 min, 45 min, 2 h, 5 h, 10 h, 24 h, 48 h).
  • Retries keep the same id and increment attempt_count: dedupe on id.

Environment Variables

RECRUITEE_WEBHOOK_SECRET=your_webhook_secret   # Settings > Apps and Plugins > Webhooks

Local Development

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

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →