Agent skill

Outhire Webhooks Skill

Receive and verify Outhire (outhire.ai) AI recruiting webhooks, signed with Standard Webhooks. Use when setting up an Outhire webhook endpoint under Settings > Webhooks, getting the whsec_ signing secret, debugging webhook-id / webhook-timestamp / webhook-signature verification, or handling candidate.applied, candidate.stage_changed and webhook.ping events.

Install this skill

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


Outhire is an AI recruiting platform. Its outbound webhooks push candidate lifecycle events (a candidate applies, a candidate changes pipeline stage) to your systems. Each delivery is a JSON POST signed with Standard Webhooks: webhook-id, webhook-timestamp and webhook-signature headers and a per-webhook whsec_ secret.

When to Use This Skill

  • How do I receive Outhire webhooks?
  • How do I verify an Outhire webhook signature?
  • Where do I find my Outhire webhook signing secret?
  • How do I handle candidate.applied or candidate.stage_changed events?
  • Why is my Outhire webhook-signature verification failing?
  • How do I dedupe Outhire webhooks when the payload has no event ID?

Verification (core)

HeaderValue
webhook-idUnique ID of this delivery
webhook-timestampUnix seconds when the request was sent
webhook-signatureOne or more space-separated v1,<base64 HMAC-SHA256> entries

Signed content is {webhook-id}.{webhook-timestamp}.{raw body}. The key is the base64-decoded part of the secret after whsec_; keep the full whsec_ value in OUTHIRE_WEBHOOK_SECRET. The examples use standardwebhooks (npm, PyPI), which compares in constant time against every v1, entry. Hookdeck also accepts the svix-* spelling of the same headers, so the examples read webhook-* and fall back to svix-id, svix-timestamp and svix-signature when webhook-id is absent.

const { Webhook } = require('standardwebhooks');

// rawBody: exact request body string, NOT re-serialised JSON
function verifyOuthireWebhook(rawBody, get, secret = process.env.OUTHIRE_WEBHOOK_SECRET) {
  const prefix = get('webhook-id') ? 'webhook' : 'svix'; // svix-* fallback
  return new Webhook(secret).verify(rawBody, {
    'webhook-id': get(`${prefix}-id`),
    'webhook-timestamp': get(`${prefix}-timestamp`),
    'webhook-signature': get(`${prefix}-signature`),
  }); // returns the parsed event; throws → respond 400
}

Python: from standardwebhooks.webhooks import Webhook, then Webhook(secret).verify(await request.body(), {...same mapping...}).

Retries and the 5-minute window. The library always enforces a fixed 5-minute timestamp tolerance. Hookdeck retries and replays resend the original timestamp, so the library rejects a late retry. If you receive Outhire webhooks through Hookdeck, use the manual verifier in references/verification.md (HMAC-SHA256 over {id}.{timestamp}.{raw body}, every v1, entry compared in constant time) where the window can be dropped or widened.

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

Event Envelope

{ "event": "candidate.stage_changed", "occurred_at": "2026-03-11T00:00:00.000Z",
  "candidate": { "id": 123, "name": "Jane Doe" }, "job": { "id": 456, "title": "Account Executive" },
  "company": { "id": 789 },
  "stage_change": { "previous_stage": "applied", "new_stage": "interview", "changed_by": "user" } }

The top-level field is event. There is no event ID in the body: dedupe on the webhook-id header.

Common Event Types

EventFired when
candidate.appliedA candidate submits a self-serve application (not candidates created via API or other intake)
candidate.stage_changedA candidate moves between pipeline stages (changed_by: user, joel or api)
webhook.pingAn admin clicks Send test ping. A signed delivery, not a challenge

These three are the documented events; the list may not be exhaustive, so acknowledge unknown events with 2xx.

Delivery Semantics

  • Return a 2xx within 10 seconds. Enqueue the event and process it asynchronously.
  • Outhire keys each delivery and doesn't resend one that already succeeded, but be idempotent anyway.
  • The docs don't describe retries or ordering. Use occurred_at if you need to order events.
  • Missing headers or a bad signature: respond 400. Handlers fail closed (500) when OUTHIRE_WEBHOOK_SECRET is unset.

Environment Variables

OUTHIRE_WEBHOOK_SECRET=whsec_xxxxx   # full value including the prefix; one per webhook

Get it from Settings > Webhooks after saving the webhook (admins only).

Local Development

# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 outhire --path /webhooks/outhire

Add the printed URL with Add webhook in Settings > Webhooks, subscribe to the events, then use Send test ping.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 12, 2026

View on GitHub →