Skip to main content

Overview

The Webhooks API allows you to configure endpoints that receive real-time HTTP notifications when events occur in your InstaView account. Webhooks provide an event-driven alternative to polling, enabling immediate integration with your systems.

Resource Structure

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "apiKeyId": "api-key-uuid",
  "companyId": "company-uuid",
  "url": "https://api.example.com/webhooks/instaview",
  "name": "Production Webhook",
  "description": "Receives interview completion notifications for ATS integration",
  "headers": [
    { "name": "Authorization", "isMasked": true },
    { "name": "X-Custom-Header", "isMasked": false }
  ],
  "events": ["interview.completed", "analysis.completed"],
  "isActive": true,
  "maxRetries": 3,
  "timeoutMs": 30000,
  "consecutiveFailures": 0,
  "circuitOpenedAt": null,
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:30:00Z"
}

Required Scopes

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

Creating Webhooks

Basic Webhook

curl -X POST https://api.instaview.sk/webhooks \
  -H "Authorization: Bearer sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com/webhooks/instaview",
    "events": [3, 0]
  }'

Comprehensive Webhook Configuration

const comprehensiveWebhook = {
  // Required: Endpoint URL
  url: 'https://api.example.com/webhooks/instaview',
  
  // Required: Event types to subscribe to
  events: [
    0,  // analysis.completed
    1,  // analysis.failed
    2,  // ping (for testing)
    3,  // interview.completed
    4,  // interview.failed
    5   // interview.started
  ],
  
  // Optional: Human-readable name
  name: 'Production Analysis Webhook',
  
  // Optional: Description
  description: 'Receives all interview and analysis events for our ATS integration',
  
  // Optional: Company ID for per-company webhooks (ATS integrations)
  companyId: 'company-uuid', // If omitted, webhook receives events for all companies
  
  // Optional: Custom headers for authentication
  headers: [
    { name: 'Authorization', value: 'Bearer your-internal-token' },
    { name: 'X-Source', value: 'instaview' }
  ]
};

const response = await createWebhook(comprehensiveWebhook);

// Store the signing secret securely!
await secretsManager.store('INSTAVIEW_WEBHOOK_SECRET', response.signingSecret);
Important: The signingSecret is only returned once during webhook creation. Store it securely immediately. If you lose it, you must delete and recreate the webhook.

Listing Webhooks

List All Webhooks

async function listWebhooks(companyId) {
  const url = companyId 
    ? `https://api.instaview.sk/webhooks?companyId=${companyId}`
    : 'https://api.instaview.sk/webhooks';
    
  const response = await fetch(url, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });
  
  const { data } = await response.json();
  return data;
}

// List all webhooks (includes both company-specific and global webhooks)
const { data: webhooks, total } = await listWebhooks();
console.log(`Found ${total} webhook configurations`);

// List webhooks for a specific company (excludes global webhooks)
// Only returns webhooks that have the specified companyId
const { data: companyWebhooks } = await listWebhooks('company-uuid');
console.log(`Found ${companyWebhooks.length} webhooks for company`);

Monitor Webhook Health

async function checkWebhookHealth() {
  const { data: webhooks } = await listWebhooks();
  
  for (const webhook of webhooks) {
    const status = {
      name: webhook.name || webhook.id,
      active: webhook.isActive,
      failures: webhook.consecutiveFailures,
      circuitOpen: !!webhook.circuitOpenedAt
    };
    
    if (webhook.consecutiveFailures > 0) {
      console.warn(`⚠️ ${status.name}: ${webhook.consecutiveFailures} failures`);
    }
    
    if (webhook.circuitOpenedAt) {
      console.error(`🔴 ${status.name}: Circuit breaker open since ${webhook.circuitOpenedAt}`);
    }
  }
}

Updating Webhooks

Partial Update

async function updateWebhook(webhookId, updates) {
  const response = await fetch(
    `https://api.instaview.sk/webhooks/${webhookId}`,
    {
      method: 'PATCH',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(updates)
    }
  );
  
  return await response.json();
}

// Update URL
await updateWebhook('webhook-uuid', {
  url: 'https://api.example.com/webhooks/instaview-v2'
});

// Add more events
await updateWebhook('webhook-uuid', {
  events: [0, 1, 2, 3, 4, 5] // All events
});

// Disable webhook
await updateWebhook('webhook-uuid', {
  isActive: false
});

Update Custom Headers

// Replace all custom headers
await updateWebhook('webhook-uuid', {
  headers: [
    { name: 'Authorization', value: 'Bearer new-token' },
    { name: 'X-Environment', value: 'production' }
  ]
});
When updating headers, the entire headers array is replaced. Include all headers you want to keep.

Event Types

Event Type Reference

IDLabelDescriptionPayload
0analysis.completedAnalysis finished successfullyInterview, call attempt, candidate, job IDs
1analysis.failedAnalysis processing failedIDs + error message
2pingTest event for connectivityTest message
3interview.completedInterview finished with analysisInterview, call attempt, candidate, job IDs
4interview.failedTechnical failure during interviewIDs + error message
5interview.startedInterview session beganInterview, call attempt, candidate, job IDs

Event Selection Strategy

Subscribe only to completion events:
{
  events: [3, 0] // interview.completed, analysis.completed
}
Use case: Simple integrations that only need final results

Circuit Breaker

The circuit breaker protects both systems from cascading failures:

How It Works

1

Failures Accumulate

Each failed delivery increments consecutiveFailures
2

Circuit Opens

After threshold failures, circuitOpenedAt is set and deliveries stop
3

Deliveries Paused

No new deliveries are attempted while circuit is open
4

Manual Reset

Use the reset endpoint to re-enable after fixing issues

Monitoring Circuit State

async function monitorCircuitBreakers() {
  const { data: webhooks } = await listWebhooks();
  
  for (const webhook of webhooks) {
    if (webhook.circuitOpenedAt) {
      console.error(`Circuit OPEN: ${webhook.name}`);
      console.error(`  Opened at: ${webhook.circuitOpenedAt}`);
      console.error(`  Failures: ${webhook.consecutiveFailures}`);
      
      // Alert your team
      await sendAlert({
        type: 'webhook_circuit_open',
        webhook: webhook.name,
        url: webhook.url,
        openedAt: webhook.circuitOpenedAt
      });
    }
  }
}

Resetting Circuit Breaker

async function resetCircuitBreaker(webhookId) {
  const response = await fetch(
    `https://api.instaview.sk/webhooks/${webhookId}/reset-circuit`,
    {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }
  );
  
  const { data } = await response.json();
  console.log(`Circuit reset. Active: ${data.isActive}`);
  return data;
}

Testing Webhooks

Send Test Ping

async function testWebhook(webhookId) {
  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(`   Delivery ID: ${data.deliveryId}`);
    console.log(`   HTTP Status: ${data.httpStatus}`);
    console.log(`   Duration: ${data.durationMs}ms`);
  } else {
    console.error('❌ Webhook test failed');
    console.error(`   Message: ${data.message}`);
  }
  
  return data;
}
The webhook must be subscribed to the ping event (type 2) for testing to work.

Test Response Structure

{
  "success": true,
  "deliveryId": "delivery-uuid",
  "httpStatus": 200,
  "durationMs": 156,
  "message": "Webhook test successful!"
}
Or on failure:
{
  "success": false,
  "message": "Webhook test failed: Connection timeout"
}

Deleting Webhooks

Delete a Webhook

async function deleteWebhook(webhookId) {
  const response = await fetch(
    `https://api.instaview.sk/webhooks/${webhookId}`,
    {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }
  );
  
  if (response.status === 204) {
    console.log('Webhook deleted successfully');
  }
}
Deleting a webhook immediately stops all deliveries. Pending deliveries will not be sent.

Custom Headers

Supported Headers

You can add custom headers for authentication or routing:
{
  headers: [
    // Authentication
    { name: 'Authorization', value: 'Bearer your-token' },
    { name: 'X-API-Key', value: 'your-api-key' },
    
    // Custom headers
    { name: 'X-Source', value: 'instaview' },
    { name: 'X-Environment', value: 'production' }
  ]
}

Sensitive Headers

The following headers are automatically encrypted at rest:
  • Authorization
  • X-API-Key
  • X-Secret
  • API-Key
When listing webhooks, these headers show isMasked: true and their values are hidden.

Header Limits

  • Maximum 50 custom headers per webhook
  • Header names: 1-255 characters
  • Header values: 1-4096 characters

Configuration Fields

url
string
required
The HTTPS endpoint URL to receive webhook notifications (max 2048 characters)
events
array
required
Array of event type IDs to subscribe to (0-5)
name
string
Human-readable name for the webhook (max 255 characters)
description
string
Description of the webhook’s purpose
headers
array
Custom headers to include in requests (max 50 headers)
isActive
boolean
Whether the webhook is active (default: true, can be updated)

Response Fields

id
string
Unique identifier for the webhook configuration
apiKeyId
string
The API key ID this webhook belongs to
companyId
string
Company ID this webhook is associated with. Null if webhook receives events for all companies (global webhook).
maxRetries
number
Maximum retry attempts for failed deliveries (system-configured)
timeoutMs
number
Request timeout in milliseconds (system-configured, default 30000)
consecutiveFailures
number
Current count of consecutive delivery failures
circuitOpenedAt
string
ISO 8601 timestamp when circuit breaker was triggered (null if closed)
signingSecret
string
HMAC signing secret (only returned on creation, hex-encoded)

Error Scenarios

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "url must be a valid URL",
    "details": {
      "field": "url"
    }
  }
}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid event type",
    "details": {
      "field": "events",
      "provided": [99],
      "allowed": [0, 1, 2, 3, 4, 5]
    }
  }
}
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Webhook configuration not found",
    "details": {
      "resourceType": "WebhookConfig",
      "resourceId": "invalid-uuid"
    }
  }
}
{
  "error": {
    "code": "RESOURCE_ACCESS_DENIED",
    "message": "Access denied to this webhook configuration"
  }
}
{
  "success": false,
  "message": "Webhook is not subscribed to ping events. Add event type 2 (ping) to the webhook configuration."
}

Common Patterns

Webhook Setup Flow

async function setupWebhook(config) {
  // 1. Create webhook
  const response = await createWebhook({
    url: config.url,
    name: config.name,
    events: [2, 3, 0], // ping, interview.completed, analysis.completed
    headers: config.headers
  });
  
  // 2. Store signing secret securely
  await secretsManager.store(
    `WEBHOOK_SECRET_${response.id}`,
    response.signingSecret
  );
  
  // 3. Test connectivity
  const testResult = await testWebhook(response.id);
  
  if (!testResult.success) {
    // Clean up if test fails
    await deleteWebhook(response.id);
    throw new Error(`Webhook test failed: ${testResult.message}`);
  }
  
  // 4. Remove ping event if not needed for production
  await updateWebhook(response.id, {
    events: [3, 0] // interview.completed, analysis.completed
  });
  
  return response;
}

Health Check Automation

async function webhookHealthCheck() {
  const { data: webhooks } = await listWebhooks();
  const issues = [];
  
  for (const webhook of webhooks) {
    // Check for failures
    if (webhook.consecutiveFailures > 5) {
      issues.push({
        type: 'high_failures',
        webhook: webhook.name,
        failures: webhook.consecutiveFailures
      });
    }
    
    // Check for open circuits
    if (webhook.circuitOpenedAt) {
      issues.push({
        type: 'circuit_open',
        webhook: webhook.name,
        openedAt: webhook.circuitOpenedAt
      });
    }
    
    // Check for inactive webhooks
    if (!webhook.isActive) {
      issues.push({
        type: 'inactive',
        webhook: webhook.name
      });
    }
  }
  
  return issues;
}

Best Practices

Use Descriptive Names

Name webhooks clearly (e.g., “Production ATS Sync”, “Staging Test”)

Monitor Failures

Set up alerts for consecutiveFailures > 0

Test Before Production

Always test webhooks with ping before relying on them

Secure Secrets

Use a secrets manager for signing secrets

Next Steps