Webhooks let your system react to interview lifecycle events in real
time — session.completed is the most-listened event, but you can subscribe
to anything from session creation through scorecard finalization.
Registering an endpoint
Open Developer settings
Settings → Developer → Webhooks inside your workspace.
Add an endpoint
Paste your URL. Pick the events you want delivered. Save.
Copy the signing secret
A 32-byte secret is generated for your endpoint. Store it in your
secret manager — you’ll use it to verify HMAC signatures on every
incoming request.
Webhook endpoints are managed in the dashboard under Developer → Webhooks —
add the URL, choose the events, and copy the signing secret.
Event types
| Event | When it fires |
|---|
session.created | A new session row is created (API call or apply-page submission). |
session.started | Candidate joined; AI is interviewing. |
session.completed | Conversation ended naturally. Scorecard not necessarily ready yet. |
session.scored | Scorecard finalized. Listen for this if you want the per-dimension breakdown. |
session.failed | Candidate disconnected mid-interview or tech failure interrupted. |
session.cancelled | Recruiter cancelled before the candidate joined. |
participant.created | A new participant was added (via apply flow or API). |
application.approved | HR approved an application — candidate can be scheduled. |
application.rejected | HR declined an application. |
Payload shape
Every webhook delivers a JSON body with a consistent envelope:
{
"id": "evt_a1b2c3d4",
"event": "session.scored",
"occurred_at": "2026-06-02T08:21:47Z",
"data": {
"session": {
"id": "e3a1c2d4-...",
"status": "completed",
"score": "8.2",
"passed": true,
"recommendation": "hire",
"evaluation_breakdown": [ /* per-dimension */ ],
"transcript_url": "https://…",
"recording_url": "https://…",
"authenticity_signals": { /* … */ },
"candidate_id": "ba4f2cdd-...",
"evaluation_template_id": "ce1cd564-...",
"external_id": "greenhouse-8821"
}
}
}
The data shape varies by event — for participant.* events, you get
the participant; for application.*, the application.
Idempotency: deduplicate by id. The same logical event may be
delivered more than once during retry storms.
Verifying the signature
Every request carries a intervyo-signature header with an HMAC-SHA256
signature of the raw body, signed with your endpoint’s secret:
intervyo-signature: t=1735689600,v1=2c8ee5d1b5…
Verify before trusting the payload — anyone could post to your URL
otherwise:
import crypto from "crypto";
function verify(rawBody: string, signatureHeader: string, secret: string) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((kv) => kv.split("=") as [string, string]),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(parts.v1, "hex"),
Buffer.from(expected, "hex"),
);
}
Verify against the raw body — don’t deserialize, re-serialize, and
hash. JSON whitespace changes break the signature. In Express, capture
the raw body with express.raw({ type: "application/json" }) before
parsing.
Retries
If your endpoint returns a non-2xx status (or times out at 10 seconds),
the platform retries with exponential backoff:
| Attempt | Delay |
|---|
| 1 | immediate |
| 2 | 30 s |
| 3 | 2 min |
| 4 | 10 min |
| 5 | 1 hour |
| 6 | 6 hours |
| 7 | 24 hours |
After 7 failed attempts the event is dropped and a webhook.delivery_failed
metric is incremented on your workspace. Subsequent events are still
attempted — failures don’t suspend the endpoint.
Ordering
Events are delivered in order per session. You’re guaranteed to see
session.created before session.started, and session.scored after
session.completed. Across sessions there’s no global ordering — events
for session A can arrive interleaved with events for session B.
Testing your endpoint
The webhook settings page has a Send test event button that fires
a session.scored payload with synthetic data. Useful for verifying your
endpoint + signature verification before live traffic hits it.
You can also re-deliver any past event from the Logs tab on the
endpoint detail page — useful when you’ve fixed a bug and want to backfill
the events you missed.