Webhooks
BetaReceive signed `result.completed` events from the NTRVSTA External Integrations API.
Use webhooks to receive completed interview results automatically instead of polling the API.
At a glance
Receive completed interview results automatically
Webhooks are the best fit when another system needs to react as soon as an
interview result is ready. Build your endpoint around signature
verification, idempotency, and a fast 2xx response.
At A Glance
NTRVSTA currently sends one partner-facing webhook event:
result.completed
This event is delivered when a completed final interview result becomes available for an organization with webhook delivery enabled.
Incomplete interview results do not emit result.completed.
Use it when you want to:
- update your ATS or recruiting system as soon as an interview is completed
- ingest result metadata without polling
GET /v1/interview-results - trigger downstream review, scoring, or workflow automation
Before you start
Webhook delivery is configured at the organization level in the NTRVSTA product:
- Go to
Settings -> Integrations - Set one HTTPS webhook endpoint URL for the organization
- Save or rotate the signing secret from the same screen
Important constraints:
- Only one outbound webhook endpoint is configured per organization
- The event list is fixed today and currently contains only
result.completed - The callback URL must be a public HTTPS endpoint
- Private, link-local, and other non-public destinations are rejected during configuration and again before delivery
- Delivery resolves and validates the destination before connecting, pins the connection to that public address, does not follow redirects, and applies a five-second request deadline
What you should build
Your webhook endpoint should do four things:
- accept
POSTrequests with a raw JSON body - verify the
X-Ntrvsta-Signatureheader before trusting the payload - de-duplicate on
resultIdorX-Ntrvsta-Idempotency-Key - return
2xxquickly and process downstream work asynchronously
If you only implement one safety check, make it signature verification.
Delivery headers
Each delivery includes these headers:
Content-Type: application/json
X-Ntrvsta-Event: result.completed
X-Ntrvsta-Event-Id: external_result_completed_<resultId>
X-Ntrvsta-Idempotency-Key: <resultId>
X-Ntrvsta-Signature: t=<unixTimestamp>,v1=<hexDigest>Header semantics:
X-Ntrvsta-Eventis the event typeX-Ntrvsta-Event-Idis the stable event id for that resultX-Ntrvsta-Idempotency-Keyis the stableresultIdX-Ntrvsta-Signaturecontains the timestamp and HMAC digest
Verify the signature
Webhook deliveries are signed with HMAC SHA-256 using your organization’s webhook signing secret.
The digest is computed over:
<timestamp>.<raw_json_body>Express example
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use('/webhooks/ntrvsta', express.raw({ type: 'application/json' }));
app.post('/webhooks/ntrvsta', (req, res) => {
const signatureHeader = req.headers['x-ntrvsta-signature'];
if (!verifySignature(req.body, signatureHeader, process.env.NTRVSTA_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
if (event.eventType === 'result.completed') {
handleResultCompleted(event);
}
return res.status(200).send('OK');
});
function verifySignature(rawBody, header, secret) {
if (!header || typeof header !== 'string' || !secret) return false;
const parts = Object.fromEntries(
header.split(',').map((part) => {
const [key, value] = part.split('=');
return [key, value];
})
);
const timestamp = parts.t;
const receivedDigest = parts.v1;
if (!timestamp || !receivedDigest) return false;
const timestampMs = Number(timestamp) * 1000;
if (!Number.isFinite(timestampMs)) return false;
if (Math.abs(Date.now() - timestampMs) > 5 * 60 * 1000) return false;
const expectedDigest = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody.toString('utf8')}`)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(receivedDigest, 'hex'),
Buffer.from(expectedDigest, 'hex')
);
} catch {
return false;
}
}Event payload
result.completed deliveries use this payload shape:
{
"eventId": "external_result_completed_result_123",
"eventType": "result.completed",
"occurredAt": "2026-05-11T12:00:00.000Z",
"idempotencyKey": "result_123",
"resultId": "result_123",
"template": {
"id": "organizationInterviewId",
"name": "Senior Backend Engineer"
},
"candidate": {
"id": "candidateUserId",
"email": "candidate@example.com",
"firstName": "Ada",
"lastName": "Lovelace"
},
"interview": {
"id": "userInterviewId",
"completedAt": "2026-05-11T12:00:00.000Z",
"status": "COMPLETED"
},
"resultUrl": "https://app.ntrvsta.com/shared/interview-details/<token>",
"thumbnail": {
"contentType": "image/jpeg",
"dataBase64": "/9j/4AAQSkZJRgABAQ..."
},
"result": {
"resultId": "result_123",
"aiAnalysis": {
"scoring": {
"technical_score": 84,
"technical_feedback": "Strong backend fundamentals with solid tradeoff reasoning.",
"soft_skills_score": 81,
"soft_skills_feedback": "Communicates clearly and structures answers well.",
"language_proficiency": 90,
"language_proficiency_feedback": "Fluent and easy to follow throughout the interview.",
"weighted_score": 83.1,
"feedback": "Strong overall performance with a few areas to deepen.",
"highlights": [
"Clear system-design explanations",
"Strong debugging mindset"
],
"suggestions": [
"Go deeper on scaling tradeoffs",
"Add more concrete production examples"
],
"duration": 1420
}
}
}
}The partner payload intentionally excludes internal-only analysis fields such as transcripts, chapter breakdowns, whiteboard analysis, translation artifacts, and debug-only scoring data.
Thumbnail handling
thumbnail contains the canonical published candidate thumbnail itself, not a public or expiring URL.
contentTypeis alwaysimage/jpegdataBase64is the Base64-encoded image- NTRVSTA accepts only a strictly decodable
image/jpegobject no larger than 1,000,000 bytes, no larger than 16 megapixels, and within a 64 MiB decoder-memory budget, so the Base64 representation is up to approximately 1.34 MB - A missing, oversized, malformed, or resource-intensive thumbnail fails the delivery through the retry and dead-letter-queue path instead of sending a result without its image
When you decode it:
- verify
X-Ntrvsta-Signatureagainst the raw request body first - validate the decoded JPEG and its size again
- write it to your own private object storage using the stable result or application mapping
- delete the encoded value after persistence
The image is sensitive candidate data. Do not log the body or the Base64 value, do not store the object publicly, and do not retain the encoded value longer than needed.
NTRVSTA does not expose a webhook PDF field or a partner thumbnail/PDF retrieval route.
Key fields
The fields most integrations care about first are:
eventTypeConfirms the event type. Today this is alwaysresult.completed.resultIdStable id for the completed result and the best idempotency key for your side.template.idThe template or assessment identifier associated with the result.candidate.emailThe most common field for mapping the result back to your recruiting system.interview.completedAtTimestamp included in the webhook payload for the completed interview event.resultUrlLink to the shared result view in NTRVSTA.thumbnailInline candidate thumbnail delivered as Base64 JPEG data. See Thumbnail handling.result.aiAnalysis.scoringPublic scoring summary for downstream consumption.
Delivery semantics
NTRVSTA treats resultId as the webhook idempotency key.
Implications:
- repeated deliveries for the same completed result use the same
resultId - a delivery that was already marked as delivered is not emitted again
- consumers should de-duplicate on
resultIdorX-Ntrvsta-Idempotency-Key
Delivery is asynchronous. Final result processing writes a delivery record, then places an identifier-only job on an encrypted FIFO queue. The worker reloads the current organization configuration and persisted result before each attempt.
- HTTP
2xxmarks the deliveryDELIVERED - HTTP
4xxis terminal and is not retried - HTTP
3xx, HTTP5xx, timeouts, and network failures are retried by the queue - After four receives, an unsuccessful job moves to an encrypted dead-letter queue for operator inspection
- Disabling or removing the webhook configuration before a queued attempt marks that delivery terminal without making an HTTP request
- If the initial enqueue fails, result processing still completes and the durable delivery record remains recoverable. A one-minute scheduled sweep re-enqueues stale
PENDINGrecords and initial-enqueueFAILEDrecords before their first delivery attempt, using the same stable delivery and idempotency keys - After a delivery reaches
FAILED, an organization admin can choose Retry in the webhook delivery health table. The operation resets that delivery toPENDINGwith zero attempts, and the scheduled sweep re-enqueues it after the normal one-minute delay. The stable delivery and idempotency keys stay unchanged - recent delivery status, history, and health are visible in
Settings -> Integrations
A worker retry can repeat an HTTP request if your endpoint accepted it but the worker failed before recording the response. Deduplicate on X-Ntrvsta-Idempotency-Key.
Recommended handler flow
Your webhook endpoint should:
- return
2xxafter successful verification and processing - persist or enqueue work quickly
- avoid long synchronous processing before responding
Recommended pattern:
- verify the signature
- persist or enqueue the payload
- return
200 - process downstream work asynchronously
Webhooks vs Polling
Use webhooks when you want near-real-time result ingestion.
Use GET /v1/interview-results when:
- you need a pull-based recovery path
- you want to reconcile a missed webhook
- your system processes results on a schedule instead of immediately
The two surfaces return different payload shapes. Webhook deliveries include the full event payload above — candidate identity, template, interview timing, resultUrl, and the aiAnalysis.scoring block. GET /v1/interview-results returns only a minimal summary (resultUrl and overallScore), so plan to use webhooks as the primary path for downstream scoring and feedback.