Webhooks
Receive real-time interview and candidate events from NTRVSTA.
Webhooks for Interview Events
Webhooks allow you to receive real-time notifications when events occur in NTRVSTA, enabling you to integrate interview data with your existing systems and workflows.
Overview
What are Webhooks?
Webhooks are HTTP callbacks that NTRVSTA sends to your application when specific events occur. Instead of polling our API for changes, webhooks push data to your system immediately when events happen.
Benefits
- Real-time updates without polling
- Reduced API calls and rate limiting
- Immediate data processing capabilities
- Seamless integration with existing systems
Supported Events
Interview Events
interview.created
Triggered when a new interview is created.
{
"event": "interview.created",
"timestamp": "2024-03-15T10:30:00Z",
"data": {
"interview": {
"id": "int_1234567890",
"title": "Senior Frontend Developer",
"type": "technical",
"duration": 60,
"created_at": "2024-03-15T10:30:00Z",
"created_by": "user_123"
}
}
}interview.updated
Triggered when interview settings are modified.
interview.deleted
Triggered when an interview is deleted.
Session Events
session.started
Triggered when a candidate begins an interview session.
{
"event": "session.started",
"timestamp": "2024-03-15T14:00:00Z",
"data": {
"session": {
"id": "sess_9876543210",
"interview_id": "int_1234567890",
"candidate": {
"id": "cand_555666777",
"email": "candidate@example.com",
"name": "John Doe"
},
"started_at": "2024-03-15T14:00:00Z",
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0..."
}
}
}session.completed
Triggered when a candidate completes an interview.
{
"event": "session.completed",
"timestamp": "2024-03-15T15:00:00Z",
"data": {
"session": {
"id": "sess_9876543210",
"interview_id": "int_1234567890",
"candidate": {
"id": "cand_555666777",
"email": "candidate@example.com",
"name": "John Doe"
},
"completed_at": "2024-03-15T15:00:00Z",
"duration_minutes": 58,
"overall_score": 85,
"status": "completed"
}
}
}session.abandoned
Triggered when a candidate leaves without completing the interview.
Candidate Events
candidate.invited
Triggered when a candidate is invited to an interview.
candidate.reminded
Triggered when a reminder is sent to a candidate.
candidate.updated
Triggered when candidate information is modified.
Setting Up Webhooks
Creating a Webhook Endpoint
Basic Express.js Example
const express = require('express');
const crypto = require('crypto');
const app = express();
// Middleware to capture raw body for signature verification
app.use('/webhooks', express.raw({ type: 'application/json' }));
app.post('/webhooks/ntrvsta', (req, res) => {
const signature = req.headers['x-ntrvsta-signature'];
const payload = req.body;
// Verify webhook signature
if (!verifySignature(payload, signature)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
// Process the event
handleWebhookEvent(event);
res.status(200).send('OK');
});
function verifySignature(payload, signature) {
const secret = process.env.NTRVSTA_WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
function handleWebhookEvent(event) {
switch (event.event) {
case 'session.completed':
handleSessionCompleted(event.data.session);
break;
case 'session.started':
handleSessionStarted(event.data.session);
break;
default:
console.log(`Unhandled event: ${event.event}`);
}
}Registering Webhooks
Using the API
const webhook = await client.webhooks.create({
url: 'https://your-app.com/webhooks/ntrvsta',
events: [
'session.completed',
'session.started',
'candidate.invited'
],
secret: 'your_webhook_secret_here',
active: true
});
console.log('Webhook created:', webhook.id);Using the Dashboard
- Navigate to Settings → Webhooks
- Click "Add Webhook"
- Enter your endpoint URL
- Select events to subscribe to
- Generate or enter a secret key
- Click "Create Webhook"
Event Processing
Handling Different Event Types
Session Completion Handler
async function handleSessionCompleted(session) {
try {
// Update your database
await updateCandidateStatus(session.candidate.id, 'completed');
// Send notification to hiring manager
await sendNotification({
type: 'interview_completed',
candidate: session.candidate.name,
score: session.overall_score,
interview_id: session.interview_id
});
// Trigger next steps in hiring pipeline
await triggerNextSteps(session);
} catch (error) {
console.error('Error processing session completion:', error);
// Implement retry logic or error handling
}
}Candidate Invitation Handler
async function handleCandidateInvited(candidate) {
try {
// Log invitation in your system
await logCandidateInvitation(candidate);
// Update ATS status
await updateATSStatus(candidate.id, 'interview_invited');
// Schedule follow-up reminders
await scheduleReminders(candidate);
} catch (error) {
console.error('Error processing candidate invitation:', error);
}
}Error Handling and Retries
Implementing Retry Logic
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000; // 1 second
async function processWebhookWithRetry(event, retryCount = 0) {
try {
await processWebhookEvent(event);
} catch (error) {
if (retryCount < MAX_RETRIES) {
console.log(`Retrying webhook processing (attempt ${retryCount + 1})`);
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, retryCount)));
return processWebhookWithRetry(event, retryCount + 1);
} else {
console.error('Max retries exceeded for webhook event:', event.id);
// Log to error tracking system
await logWebhookError(event, error);
}
}
}Security
Signature Verification
Why Verify Signatures?
- Prevent spoofing of webhook events
- Ensure data integrity during transmission
- Authenticate that events come from NTRVSTA
Verification Implementation
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
// Use timing-safe comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// Usage in webhook handler
app.post('/webhooks/ntrvsta', (req, res) => {
const signature = req.headers['x-ntrvsta-signature'];
const payload = req.body;
const secret = process.env.NTRVSTA_WEBHOOK_SECRET;
if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook...
});Best Practices
Endpoint Security
- Use HTTPS for all webhook endpoints
- Validate signatures on every request
- Rate limit webhook endpoints
- Log security events for monitoring
Secret Management
- Rotate secrets regularly
- Store secrets securely (environment variables, key vaults)
- Use different secrets for different environments
- Monitor for unauthorized access attempts
Testing Webhooks
Local Development
Using ngrok for Testing
# Install ngrok
npm install -g ngrok
# Start your local server
node server.js
# In another terminal, expose your local server
ngrok http 3000
# Use the ngrok URL for webhook registration
# https://abc123.ngrok.io/webhooks/ntrvstaTesting with curl
# Test your webhook endpoint
curl -X POST https://your-app.com/webhooks/ntrvsta \
-H "Content-Type: application/json" \
-H "X-NTRVSTA-Signature: your_test_signature" \
-d '{
"event": "session.completed",
"timestamp": "2024-03-15T15:00:00Z",
"data": {
"session": {
"id": "test_session_123",
"overall_score": 85
}
}
}'Webhook Testing Tools
Built-in Test Events
// Trigger test webhook events
await client.webhooks.test({
webhookId: 'webhook_123',
event: 'session.completed',
testData: {
session: {
id: 'test_session_456',
overall_score: 92
}
}
});Webhook Debugging
// Enable webhook debugging
const webhook = await client.webhooks.update('webhook_123', {
debug: true,
logLevel: 'detailed'
});
// View webhook delivery logs
const logs = await client.webhooks.getLogs('webhook_123', {
limit: 50,
status: 'failed' // or 'success', 'pending'
});Monitoring and Troubleshooting
Webhook Delivery Status
Checking Delivery Status
// Get webhook delivery statistics
const stats = await client.webhooks.getStats('webhook_123', {
timeRange: '24h'
});
console.log('Delivery stats:', {
total: stats.total_deliveries,
successful: stats.successful_deliveries,
failed: stats.failed_deliveries,
success_rate: stats.success_rate
});Failed Delivery Handling
// Get failed deliveries for investigation
const failedDeliveries = await client.webhooks.getFailedDeliveries('webhook_123', {
limit: 20,
since: '2024-03-15T00:00:00Z'
});
failedDeliveries.forEach(delivery => {
console.log(`Failed delivery: ${delivery.id}`);
console.log(`Error: ${delivery.error_message}`);
console.log(`Retry count: ${delivery.retry_count}`);
});Common Issues and Solutions
Issue: Webhook Not Receiving Events
Possible Causes:
- Incorrect URL configuration
- Firewall blocking requests
- SSL certificate issues
- Server not responding
Solutions:
// Test webhook connectivity
const testResult = await client.webhooks.testConnectivity('webhook_123');
if (!testResult.success) {
console.log('Connection test failed:', testResult.error);
}Issue: Signature Verification Failing
Possible Causes:
- Incorrect secret key
- Body modification by middleware
- Character encoding issues
Solutions:
// Debug signature verification
function debugSignatureVerification(payload, receivedSignature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
console.log('Expected signature:', expectedSignature);
console.log('Received signature:', receivedSignature);
console.log('Payload length:', payload.length);
console.log('Secret length:', secret.length);
}Integration Examples
ATS Integration
Updating Applicant Tracking System
async function syncWithATS(session) {
const atsClient = new ATSClient();
await atsClient.updateCandidate(session.candidate.id, {
interview_status: 'completed',
interview_score: session.overall_score,
interview_date: session.completed_at,
next_step: session.overall_score >= 80 ? 'technical_round_2' : 'rejected'
});
}Slack Notifications
Sending Team Notifications
async function sendSlackNotification(session) {
const slackClient = new SlackClient();
const message = {
channel: '#hiring',
text: `Interview completed for ${session.candidate.name}`,
attachments: [{
color: session.overall_score >= 80 ? 'good' : 'warning',
fields: [
{ title: 'Score', value: `${session.overall_score}/100`, short: true },
{ title: 'Duration', value: `${session.duration_minutes} minutes`, short: true }
]
}]
};
await slackClient.sendMessage(message);
}Webhooks provide a powerful way to integrate NTRVSTA with your existing hiring workflow, enabling real-time updates and automated processes.
Authentication & First Request
Make your first successful call to the NTRVSTA External Integrations API.
Create or reuse a candidate interview invitation POST
Creates a candidate interview invitation for a template owned by the authenticated organization, or reuses the existing mapping for the same candidate and template. The canonical payload sends candidate identity inside the nested `candidate` object. Legacy top-level candidate fields remain accepted for compatibility, but new clients should prefer the nested shape.