HiringCenter

HiringCenter Webhooks Overview

HiringCenter Outbound Webhooks

HiringCenter can notify your systems in real time when events happen in your account—for example when a prospect is created or a task is completed. The platform POSTs signed JSON to HTTPS URLs you configure. Your server is the receiver; HiringCenter is the caller.

This is separate from the REST API paths you call with an API key. For REST resources (prospects, notes, tasks, and so on), see the HiringCenter API Overview.

You can also use polling (GET /webhooks/poll) for Zapier-style integrations that pull recent prospect data instead of accepting push deliveries.


Configure a Webhook Subscription

Create and manage webhook subscriptions in HiringCenter as an account admin or owner:

Settings → API / Webhooks

For each subscription you provide:

  • Webhook URL — your public HTTPS endpoint (hookUrl)
  • Event types — which events to receive (for example prospect.created)
  • Endpoint secret — a per-subscription signing secret (whsec_...) used to verify deliveries

The endpoint secret is shown once when the subscription is created or rotated. Store it securely on your server (environment variable or secrets manager). HiringCenter encrypts secrets at rest and does not return the full plaintext secret on later reads.

Webhook subscription management (subscribe, rotate, test) uses Firebase authentication in the app or master-key access. It is not available with API key authentication.


Endpoint Secret

  • Format: whsec_ followed by random hex characters
  • Purpose: verify that each POST to your URL was signed by HiringCenter
  • Rotation: rotating a secret starts a 24-hour grace period during which deliveries may include signatures for both the new and previous secret (see Secret rotation)

HTTP Request Your Endpoint Receives

When an subscribed event fires, HiringCenter sends:

ItemValue
MethodPOST
Content-Typeapplication/json
User-AgentHiringCenter-Webhook/2.0
X-HiringCenter-Signaturet=<unix_seconds>,v1=<hex_hmac>
BodyJSON event envelope (see below)

Delivery rules

  • Only active subscriptions (subscribed: true) whose event list includes the fired type receive the request.
  • The target URL must be public HTTPS. Private or local hosts are rejected when the subscription is saved.
  • Request timeout is 10 seconds.
  • There is no automatic retry queue for failed deliveries.
  • The signature is computed over the exact UTF-8 bytes in the body (a single JSON.stringify on the server).

Respond with 2xx after you accept the event. You can process the payload asynchronously. Non-2xx responses are logged as failed deliveries.


Event Types

Event typeWhen it is sent
prospect.createdA prospect is created (API, import, in-app, etc.). After a short consistency delay, HiringCenter loads the prospect and dispatches unless webhooks are muted for that record.
prospect.updatedA prospect is updated. Payload includes field-level changes and the current prospect snapshot.
task.createdA task is created via the API.
task.updatedA task is updated.
task.completedA task is marked completed.
task.deletedA task is deleted.
generalTest deliveries and generic subscriptions.

These aliases normalize to prospect.created: prospect_created, prospect-created, new_prospect, new-prospect.


Event Payload Envelope

Every outbound delivery uses the same top-level shape:

JSONCode
{ "id": "evt_1717014400000_k3j9x2m1p", "type": "prospect.created", "apiVersion": "2025-10-28", "createdAt": "2024-05-29T15:20:00.000Z", "accountId": "6b3e1a9d4c7f42e8a5d0c1f9b2e7a4c6", "resource": "prospect", "resourceId": "c1a7e9d34b8f4c2aa6d15e0f7b3c9d42", "data": { } }
FieldDescription
idUnique id for this delivery (not the prospect or task id).
typeEvent type from the table above.
apiVersionPayload schema version (currently 2025-10-28).
createdAtISO 8601 timestamp when the envelope was built.
accountIdHiringCenter account that owns the resource.
resourceResource family (for example prospect for prospect.created).
resourceIdPrimary resource id when present in data (for example prospectId).
dataEvent-specific payload (shape depends on type).

prospect.created

JSONCode
{ "id": "evt_...", "type": "prospect.created", "apiVersion": "2025-10-28", "createdAt": "2024-05-29T15:20:00.000Z", "accountId": "<accountId>", "resource": "prospect", "resourceId": "<prospectId>", "data": { "prospectId": "<prospectId>", "prospect": { "id": "<prospectId>", "firstName": "Avery", "lastName": "Coleman", "email": "avery.coleman@example.com", "phone": "15551234567", "createdAt": 1717014400000 } } }

data.prospect is the prospect record as stored in HiringCenter (firstName, lastName, email, phone, stageId, ownerId, and other fields). Additional fields may appear over time; treat unknown keys as optional.

prospect.updated

data includes:

  • prospectId
  • changes — field-level before/after values for changed fields
  • prospect — current prospect snapshot after the update

Task events

Task payloads include taskId and event-specific fields (task, updates, links, and so on). Test deliveries use sample data shaped like production events.


Suppressing Outbound Webhooks

To create a prospect without firing prospect.created webhooks, set muteWebhook on the create request:

TerminalCode
curl -X POST \ 'https://api.hiringcenterpro.com/v2/prospects' \ -H 'Authorization: Bearer <YOUR_API_KEY>' \ -H 'Content-Type: application/json' \ -d '{ "firstName": "Jane", "lastName": "Doe", "muteWebhook": true }'

HiringCenter stores muteWebhook on the prospect document. The prospect.created trigger skips POSTing to your endpoints for that record.


Signature Verification

Every outbound POST includes an X-HiringCenter-Signature header:

Code
X-HiringCenter-Signature: t=<unix_timestamp>,v1=<hex_hmac>

Each v1 value is:

Code
HMAC_SHA256(endpointSecret, t + "." + rawBody)

Where:

  • endpointSecret is your subscription secret (whsec_...)
  • t is Unix time in seconds (same value as in the header)
  • rawBody is the exact request body as received—do not parse JSON and re-serialize before verifying

Secret rotation

During a 24-hour grace period after you rotate a secret, the header may include multiple v1 values:

Code
X-HiringCenter-Signature: t=<unix_timestamp>,v1=<current_hex>,v1=<previous_hex>

Accept the request if any v1 matches a valid signature computed with the current or previous secret.

Verification steps

  1. Read the raw body before middleware mutates it (express.raw, req.text(), etc.).
  2. Read X-HiringCenter-Signature.
  3. Parse t and every v1 from the header (comma-separated key=value parts).
  4. Reject if the header is malformed or has no v1.
  5. Reject replayed requests: abs(nowUnixSeconds - t) > toleranceSeconds (recommended 300 seconds).
  6. Compute expected = HMAC_SHA256(endpointSecret, t + "." + rawBody) as lowercase hex.
  7. Compare expected to each v1 using a constant-time comparison; accept if any match.
  8. Only then JSON.parse(rawBody) and branch on event.type / event.data.

Common verification failures

  • Verifying against parsed or re-stringified JSON instead of the raw body
  • Middleware that consumes or reformats the body before verification
  • Ignoring timestamp tolerance (replay risk)
  • Comparing signatures with a normal string equality check instead of constant-time compare
  • Accepting only the first v1 during rotation grace

Node.js helper

JavascriptCode
const crypto = require('crypto'); function parseSignatureHeader(signatureHeader) { if (!signatureHeader || typeof signatureHeader !== 'string') return null; const parts = signatureHeader.split(',').map((part) => part.trim()).filter(Boolean); let timestamp = null; const signatures = []; for (const part of parts) { const [k, v] = part.split('='); if (!k || !v) continue; if (k === 't') { const parsed = Number.parseInt(v, 10); if (Number.isFinite(parsed)) timestamp = parsed; } else if (k === 'v1' && /^[0-9a-fA-F]+$/.test(v)) { signatures.push(v.toLowerCase()); } } if (!timestamp || signatures.length === 0) return null; return { timestamp, signatures }; } function verifyHiringCenterSignature({ rawBody, signatureHeader, endpointSecret, toleranceSeconds = 300 }) { const parsed = parseSignatureHeader(signatureHeader); if (!parsed) return false; const { timestamp, signatures } = parsed; const now = Math.floor(Date.now() / 1000); if (Math.abs(now - timestamp) > toleranceSeconds) return false; const signingInput = `${timestamp}.${rawBody}`; const expected = crypto.createHmac('sha256', endpointSecret).update(signingInput).digest('hex'); for (const candidate of signatures) { try { if (crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(candidate, 'hex'))) { return true; } } catch (_) {} } return false; }

Express example

JavascriptCode
const express = require('express'); const app = express(); app.post('/webhooks/hiringcenter', express.raw({ type: 'application/json' }), (req, res) => { const endpointSecret = process.env.HIRINGCENTER_WEBHOOK_SECRET; const signatureHeader = req.headers['x-hiringcenter-signature']; const rawBody = req.body.toString('utf8'); if (!verifyHiringCenterSignature({ rawBody, signatureHeader, endpointSecret })) { return res.status(400).send('Invalid signature'); } const event = JSON.parse(rawBody); if (event.type === 'prospect.created') { // handle event.data.prospect } return res.sendStatus(200); });

Polling Alternative (Zapier)

If your integration cannot accept inbound HTTPS requests, poll recent prospect data with your API key:

Base URL: https://api.hiringcenterpro.com/v2

TerminalCode
curl -X GET \ 'https://api.hiringcenterpro.com/v2/webhooks/poll?eventType=prospect.created' \ -H 'Authorization: Bearer <YOUR_API_KEY>' \ -H 'Accept: application/json'

Poll responses use a flat snake_case prospect record (first_name, last_name, created_at, and so on), not the push event envelope above. Timestamp fields in poll payloads may be ISO 8601 strings.

Supported poll eventType values currently include prospect.created (default).


Testing a Subscription

Send a production-shaped signed test delivery from HiringCenter (admin/owner in the app, or authorized internal tooling):

  • Method: POST
  • Path: /webhooks/{subscriptionId}/test

The response includes the payload, request headers (including X-HiringCenter-Signature), and the HTTP status returned by your endpoint.

Test payloads include metadata.isTest: true in data where applicable.


  • OpenAPI spec: docs/openapi.yaml (Webhooks tag and OutboundWebhookEvent schema)
  • Verification reference: docs/VERIFYING_WEBHOOKS.md
  • Security architecture: docs/SECURITY_OUTGOING_WEBHOOKS.md
Last modified on