Webhooks
Set an endpoint on your workspace page and InterviewStack sends a signed event the moment a candidate's review is ready. Two event types today, more as the API grows.
Last updated 13 September 2026.
Events
screening.completed: a candidate's interview is complete. Sent once per candidate, whether or not the assessment is attached yet (data.scoredsays which).screening.scored: the assessment is attached. Sent once per candidate: together withscreening.completedwhen scoring succeeds at submit, or later when a recruiter scores an interview the candidate never submitted. Subscribe to this one if you only want rows the scorer has processed (data.ai_signalis null in the rare case the scorer produced no number). The payload is identical.webhook.test: sent when you press "Send a test event".
Payload
JSON, Content-Type: application/json. Identifiers, statuses, the caveated AI signal, plan coverage and the number of session flags, plus a link to the review. Never the transcript, never the written assessment, never the recording: those stay behind sign-in on the review page.
{
"id": "…", // delivery id; also the Delivery header
"event": "screening.completed",
"created_at": "2026-09-08T14:03:11.000Z",
"api_version": "2026-09-08",
"data": {
"workspace_id": "…",
"screening": {
"id": "…",
"title": "Backend engineer screen",
"role": "Software Engineer",
"level": "mid_level",
"interview_type": "technical"
},
"candidate": {
"id": "…",
"name": "…",
"email": "…",
"status": "COMPLETED",
"source": "INVITE",
"started_at": "…",
"completed_at": "…",
"experience_rating": 4
},
"ai_signal": 71, // 0..100, a signal, not a decision;
// null if unscored
"scored": true,
"plan_coverage": "4/5",
"flags_count": 0,
"review_url": "https://hire.interviewstack.io/s/…/c/…"
}
}Signature
Every request carries X-InterviewStack-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of "<t>.<raw request body>" with your signing secret (shown once when you set or rotate the endpoint). Verify with the raw body, not a re-serialised one, and reject timestamps older than five minutes.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret) {
if (typeof header !== 'string') return false;
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('=')),
);
if (!parts.t || !parts.v1) return false;
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (age > 300) return false;
const expected = createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
if (expected.length !== parts.v1.length) return false;
return timingSafeEqual(
Buffer.from(expected), Buffer.from(parts.v1),
);
}Delivery and retries
Respond with any 2xx within 10 seconds. Anything else, a timeout or a redirect counts as a failure. Retries go out on the next sweep after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h, 12 h and 24 h (the sweep runs every 15 minutes, so the first retries can land up to 15 minutes late), and we give up after eight attempts. screening.completed fires at most once per candidate per endpoint, but delivery is at least once: deliveries can arrive out of order and, rarely, more than once, so de-duplicate on the delivery id. The last ten deliveries and their outcomes are listed on your workspace page.
Endpoint rules
HTTPS only, a public host, no credentials in the URL. One endpoint per workspace from the dashboard. Replacing the URL or rotating the secret takes effect immediately.