Webhooks
Get notified when things happen in your hiring pipeline. WorkSignal will POST a JSON payload to your endpoint whenever a subscribed event occurs.
Setup
Webhook subscriptions are managed from Settings → Integrations → Webhook Subscriptions in your dashboard, or via the API:
/webhooks/events
List all available event types
/webhooks
List your webhook subscriptions
/webhooks
Create a new subscription - Body: { url, events: [...] }
/webhooks/:id
Update a subscription URL or events
/webhooks/:id
Delete a subscription
/webhooks/:id/test
Send a test event to your endpoint
/webhooks/:id/pause
Pause delivery without deleting
/webhooks/:id/activate
Resume delivery
Event types
| Event | Description |
|---|---|
| application.created | New candidate applied to a role |
| application.stage_changed | Candidate moved to a new pipeline stage |
| voice_screen.completed | Voice screen finished - includes scores and transcript |
| voice_screen.failed | Voice screen failed (no-show or technical error) |
| candidate.passed | Candidate passed the screening threshold |
| candidate.rejected | Candidate rejected - manually or by auto-filter |
| candidate.hired | Candidate marked as hired |
| candidate.withdrawn | Candidate withdrew from the process |
Payload format
Every webhook delivery is an HTTP POST with Content-Type: application/json. The payload envelope is consistent across all events:
{
"event": "voice_screen.completed",
"occurred_at": "2026-04-13T14:32:00Z",
"organization_id": 7,
"data": {
"voice_screen": {
"id": 301,
"status": "completed",
"overall_score": 78,
"candidate": {
"id": 55,
"name": "Alex Petrov",
"email": "alex@example.com"
},
"role": {
"id": 42,
"title": "Senior Backend Engineer"
}
}
}
}
Verifying signatures
Every delivery includes a X-WorkSignal-Signature header - an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
Verify it on your end to confirm the payload came from WorkSignal:
# Ruby
require "openssl"
def valid_signature?(payload, signature, secret)
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, payload)
Rack::Utils.secure_compare(expected, signature)
end
# Node.js
const crypto = require("crypto")
function validSignature(payload, signature, secret) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex")
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
}
Always verify signatures
Use a constant-time comparison function (timingSafeEqual, secure_compare) to avoid timing attacks. Reject any delivery where the signature does not match.
Retries and delivery
-
-
Your endpoint must return HTTP
2xxwithin 10 seconds to acknowledge delivery. - - Failed deliveries are retried up to 5 times with exponential backoff (1 min, 5 min, 30 min, 2 hr, 8 hr).
- - After 5 failures, the subscription is automatically paused.
-
-
Each delivery includes a
X-WorkSignal-Deliveryheader with a unique delivery ID for deduplication.