Webhooks

Set an endpoint on your workspace page and InterviewStack sends a signed event the moment a candidate's review is ready. One event type today, more as the API grows.

Last updated 8 September 2026.

Events

  • screening.completed: a candidate submitted their interview and the assessment is attached (or the row is scored later by the recruiter; you get the event once).
  • 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 in X-InterviewStack-Delivery
  "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;
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 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.

Webhooks | InterviewStack for Recruiters