Skip to main content

Overview

Webhooks enable your application to receive real-time notifications when events occur in InstaView. Instead of polling the API, webhooks push data to your endpoint immediately when interviews complete, analyses finish, or other events happen.

How Webhooks Work

1

Configure Endpoint

Register your webhook URL and specify which events to receive
2

Event Occurs

An interview completes, analysis finishes, or other tracked event happens
3

Receive Notification

InstaView sends an HTTP POST request to your endpoint with event details
4

Process & Respond

Your server processes the event and returns a 2xx status code

Event Types

InstaView supports the following webhook event types:
EventDescription
interview.startedInterview session began
interview.completedInterview finished with analysis available
interview.failedTechnical failure occurred during interview

Event Type Strings

When configuring webhooks, use these event type strings:
  • "analysis.completed" - Analysis finished successfully
  • "analysis.failed" - Analysis processing failed
  • "ping" - Test event for connectivity
  • "interview.completed" - Interview finished with analysis
  • "interview.failed" - Technical failure during interview
  • "interview.started" - Interview session began

Payload Structure

All webhook payloads follow this structure:
{
  "event": "interview.completed",
  "timestamp": "2024-01-20T14:30:00.000Z",
  "deliveryId": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    // Event-specific data
  }
}

Interview Events Payload

{
  "event": "interview.completed",
  "timestamp": "2024-01-20T14:30:00.000Z",
  "deliveryId": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "interviewId": "interview-uuid",
    "callAttemptId": "call-attempt-uuid",
    "candidateId": "candidate-uuid",
    "jobId": "job-uuid",
    "status": "COMPLETED"
  }
}

Analysis Events Payload

{
  "event": "analysis.completed",
  "timestamp": "2024-01-20T14:30:00.000Z",
  "deliveryId": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "interviewId": "interview-uuid",
    "callAttemptId": "call-attempt-uuid",
    "candidateId": "candidate-uuid",
    "jobId": "job-uuid",
    "status": "COMPLETED"
  }
}

Failed Event Payloads

Failed events include an error message:
{
  "event": "interview.failed",
  "timestamp": "2024-01-20T14:30:00.000Z",
  "deliveryId": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "interviewId": "interview-uuid",
    "callAttemptId": "call-attempt-uuid",
    "candidateId": "candidate-uuid",
    "jobId": "job-uuid",
    "status": "FAILED",
    "errorMessage": "Connection timeout during call"
  }
}

Ping Event Payload

{
  "event": "ping",
  "timestamp": "2024-01-20T14:30:00.000Z",
  "deliveryId": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "message": "Test ping from InstaView webhook system"
  }
}
Webhook payloads contain minimal data (IDs and status). For full details like analysis results or transcripts, use the Interview API to fetch the complete resource.

Per-Company Webhooks

For ATS integrations managing multiple companies, you can register webhooks per company by specifying the companyId parameter when creating a webhook. This enables:
  • Company-specific endpoints: Each company can have its own webhook URL (e.g., different subdomains)
  • Selective event delivery: Webhooks with companyId only receive events for that specific company
  • Global webhooks: Webhooks without companyId receive events for all companies accessible by the API key

Example: Per-Company Setup

// Webhook for Company A
await createWebhook({
  url: 'https://company-a.yourats.com/webhooks/instaview',
  companyId: 'company-a-uuid',
  events: ['interview.completed', 'analysis.completed']
});

// Webhook for Company B
await createWebhook({
  url: 'https://company-b.yourats.com/webhooks/instaview',
  companyId: 'company-b-uuid',
  events: ['interview.completed', 'analysis.completed']
});

// Global webhook (receives all events)
await createWebhook({
  url: 'https://admin.yourats.com/webhooks/instaview',
  // No companyId - receives events for all companies
  events: ['interview.completed', 'analysis.completed']
});

Filtering by Company

When listing webhooks, you can filter by company:
// List all webhooks (includes both company-specific and global webhooks)
const allWebhooks = await fetch('/webhooks', { headers });

// List webhooks for a specific company (excludes global webhooks)
// Only returns webhooks that have the specified companyId
const companyWebhooks = await fetch('/webhooks?companyId=company-uuid', { headers });
Note: When filtering by companyId, only company-specific webhooks are returned. Global webhooks (those without a companyId) are excluded from filtered results. To see all webhooks including global ones, omit the companyId parameter.

Required Scopes

OperationRequired Scope
List webhooksread:webhooks
Get webhook by IDread:webhooks
Create webhookwrite:webhooks
Update webhookwrite:webhooks
Delete webhookwrite:webhooks
Test webhookwrite:webhooks
Reset circuit breakerwrite:webhooks

Security

Signature Verification

Every webhook request includes an HMAC-SHA256 signature in the X-Webhook-Signature header. Always verify this signature to ensure requests are from InstaView. The signature format is: sha256=<hex-encoded-signature>
const crypto = require("crypto");

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature =
    "sha256=" +
    crypto
      .createHmac("sha256", Buffer.from(secret, "hex"))
      .update(payload, "utf8")
      .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express.js example
app.post(
  "/webhooks/instaview",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["x-webhook-signature"];
    const payload = req.body.toString();

    if (
      !verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)
    ) {
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(payload);
    // Process the event...

    res.status(200).send("OK");
  }
);

Request Headers

Every webhook request includes these headers:
HeaderDescription
Content-TypeAlways application/json
X-Webhook-SignatureHMAC-SHA256 signature for payload verification
X-Webhook-Delivery-IdUnique ID for this delivery (for idempotency)
X-Webhook-EventEvent type (e.g., interview.completed)
User-AgentInstaView-Webhook/1.0
Plus any custom headers you configured when creating the webhook.

Secret Management

Store your signing secret securely! The signing secret is only returned once when you create a webhook configuration. If lost, you must delete and recreate the webhook to get a new secret.
Best practices for secret management:
  • Store secrets in environment variables or a secrets manager
  • Never commit secrets to version control
  • Rotate secrets periodically by creating new webhooks
  • Use different secrets for development and production

Retry Logic

If your endpoint fails to respond with a 2xx status code, InstaView automatically retries delivery with exponential backoff:
1

Immediate Attempt

First delivery attempt immediately when event occurs
2

Retry 1

After a short delay if first attempt fails
3

Retry 2

After a longer delay if second attempt fails
4

Retry 3

Final attempt with maximum delay
After all retries are exhausted, the delivery is marked as failed.

Circuit Breaker

To protect both your system and ours, InstaView implements a circuit breaker pattern:
  • If a webhook consistently fails, it will be automatically disabled
  • You can monitor the consecutiveFailures count in the webhook configuration
  • When disabled, the circuitOpenedAt timestamp indicates when the circuit opened
  • Use the Reset Circuit Breaker endpoint to re-enable the webhook after fixing issues

Endpoint Requirements

Your webhook endpoint must:
Return a 2xx response within 30 seconds. For long-running tasks, acknowledge receipt immediately and process asynchronously.
app.post('/webhooks/instaview', async (req, res) => {
  // Acknowledge immediately
  res.status(200).send('OK');
  
  // Process asynchronously
  processWebhookAsync(req.body).catch(console.error);
});
Handle duplicate deliveries gracefully using the deliveryId:
// Note: Use Redis or database storage in production for multi-instance deployments
const processedDeliveries = new Set();

app.post('/webhooks/instaview', (req, res) => {
  const { deliveryId } = req.body;
  
  if (processedDeliveries.has(deliveryId)) {
    return res.status(200).send('Already processed');
  }
  
  processedDeliveries.add(deliveryId);
  // Process the event...
  
  res.status(200).send('OK');
});
Production webhook URLs must use HTTPS. HTTP is only allowed for localhost during development.
Any 2xx status code indicates successful receipt. Retry behavior depends on the status code:
StatusMeaning
200, 201, 202Success - no retry
429Rate limit - will retry
4xx (except 429)Client error - no retry (permanent errors like 401, 404)
5xxServer error - will retry

Managing Webhooks

Create a Webhook

const response = await fetch("https://api.instaview.sk/webhooks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://api.yourcompany.com/webhooks/instaview",
    name: "Production Webhook",
    description: "Receives interview completion notifications",
    events: ["interview.completed", "analysis.completed"],
    companyId: "company-uuid", // Optional: Associate with specific company
    headers: [{ name: "Authorization", value: "Bearer your-internal-token" }],
  }),
});

const { data } = await response.json();

// IMPORTANT: Store this secret securely - it's only shown once!
console.log("Signing secret:", data.signingSecret);
Per-Company Webhooks: For ATS integrations managing multiple companies, you can specify companyId when creating a webhook. This allows you to register separate webhooks per company (e.g., different subdomains). If omitted, the webhook will receive events for all companies accessible by the API key (global webhook).

List Webhooks

// List all webhooks (includes both company-specific and global webhooks)
const response = await fetch("https://api.instaview.sk/webhooks", {
  headers: { Authorization: `Bearer ${apiKey}` },
});

// Filter by company ID (returns only company-specific webhooks, excludes global ones)
const responseFiltered = await fetch(
  "https://api.instaview.sk/webhooks?companyId=company-uuid",
  {
    headers: { Authorization: `Bearer ${apiKey}` },
  }
);

const { data } = await response.json();
console.log(`Found ${data.total} webhooks`);

for (const webhook of data.data) {
  console.log(`${webhook.name}: ${webhook.url}`);
  console.log(`  Company: ${webhook.companyId || "All companies"}`);
  console.log(`  Events: ${webhook.events.join(", ")}`);
  console.log(`  Active: ${webhook.isActive}`);
  console.log(`  Failures: ${webhook.consecutiveFailures}`);
}

Update a Webhook

await fetch(`https://api.instaview.sk/webhooks/${webhookId}`, {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://api.yourcompany.com/webhooks/instaview-v2",
    events: ["interview.completed", "interview.failed", "analysis.completed", "analysis.failed"],
    isActive: true,
  }),
});

Delete a Webhook

await fetch(`https://api.instaview.sk/webhooks/${webhookId}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${apiKey}` },
});

Test a Webhook

Send a test ping to verify connectivity:
const response = await fetch(
  `https://api.instaview.sk/webhooks/${webhookId}/test`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}` },
  }
);

const { data } = await response.json();

if (data.success) {
  console.log("Webhook test successful!");
  console.log(`Response time: ${data.durationMs}ms`);
} else {
  console.error("Webhook test failed:", data.message);
}
To test a webhook using the test endpoint above, it must be subscribed to the ping event.

Test Interview Events with Test Interviews

For more comprehensive testing of interview-related webhook events (interview.completed, analysis.completed), you can create test interviews using the isTest flag:
// Create a test interview that immediately completes
const testInterview = await fetch("https://api.instaview.sk/v1/public/interviews", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    candidateId: "candidate-uuid",
    agentId: "agent-uuid",
    isTest: true, // Creates test interview
  }),
});

// This will trigger:
// 1. interview.completed webhook
// 2. analysis.completed webhook
// Both events will be sent to your webhook endpoint
Test interviews are useful for:
  • Testing your webhook handler’s processing logic for interview completion
  • Verifying that analysis data is correctly parsed and stored
  • End-to-end testing of your integration without waiting for real interviews
  • Development and debugging without consuming billing minutes
See the Create Interview documentation for more details on test mode.

Reset Circuit Breaker

If your webhook was disabled due to consecutive failures:
await fetch(
  `https://api.instaview.sk/webhooks/${webhookId}/reset-circuit`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}` },
  }
);

Custom Headers

You can configure custom headers to authenticate with your endpoint:
{
  "url": "https://api.yourcompany.com/webhooks/instaview",
  "headers": [
    { "name": "Authorization", "value": "Bearer your-internal-token" },
    { "name": "X-Custom-Header", "value": "custom-value" }
  ],
  "events": ["interview.completed", "analysis.completed"]
}
Security: Sensitive headers (Authorization, X-API-Key, X-Secret, API-Key) are automatically encrypted at rest. When listing webhooks, these header values are masked for security.

Best Practices

Verify Signatures

Always verify the X-Webhook-Signature header to ensure requests are authentic

Respond Fast

Return 200 immediately and process asynchronously for long-running tasks

Handle Duplicates

Use deliveryId for idempotency - you may receive the same event multiple times

Monitor Health

Check consecutiveFailures and circuitOpenedAt to catch issues early

Troubleshooting

Possible causes: - Webhook is not subscribed to the event type - Webhook is disabled (isActive: false) - Circuit breaker is open (check circuitOpenedAt) Solutions: - Verify event subscriptions in webhook configuration - Check if webhook is active - Reset circuit breaker if needed
Possible causes: - Using wrong signing secret - Modifying payload before verification - Encoding issues Solutions: - Verify you’re using the original signing secret - Verify the raw request body, not parsed JSON - Ensure UTF-8 encoding
Cause: Too many consecutive failures triggered the circuit breaker Solutions: 1. Fix the underlying issue (endpoint availability, authentication, etc.) 2. Use the test endpoint to verify connectivity 3. Reset the circuit breaker
Possible causes: - Event occurred before webhook was configured - Event type not in subscription list Note: Webhooks only deliver events that occur after configuration. For historical data, use the API to poll for past events.

Next Steps