> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instaview.sk/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Configure and manage webhook endpoints for real-time event notifications

## 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

```json theme={null}
{
  "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

| Operation             | Required Scope   |
| --------------------- | ---------------- |
| List webhooks         | `read:webhooks`  |
| Get webhook by ID     | `read:webhooks`  |
| Create webhook        | `write:webhooks` |
| Update webhook        | `write:webhooks` |
| Delete webhook        | `write:webhooks` |
| Test webhook (ping)   | `write:webhooks` |
| Reset circuit breaker | `write:webhooks` |

## Creating Webhooks

### Basic Webhook

<CodeGroup>
  ```bash cURL theme={null}
  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": ["INTERVIEW_COMPLETED", "ANALYSIS_COMPLETED"]
    }'
  ```

  ```javascript Node.js theme={null}
  const webhook = await fetch('https://api.instaview.sk/webhooks', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.INSTAVIEW_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: 'https://api.example.com/webhooks/instaview',
      events: ["INTERVIEW_COMPLETED", "ANALYSIS_COMPLETED"]
    })
  });

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

  // IMPORTANT: Store this securely - only shown once!
  console.log('Signing secret:', data.signingSecret);
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://api.instaview.sk/webhooks',
      headers={
          'Authorization': f'Bearer {os.environ["INSTAVIEW_API_KEY"]}',
          'Content-Type': 'application/json'
      },
      json={
          'url': 'https://api.example.com/webhooks/instaview',
          'events': ['INTERVIEW_COMPLETED', 'ANALYSIS_COMPLETED']
      }
  )

  data = response.json()['data']

  # IMPORTANT: Store this securely - only shown once!
  print(f'Signing secret: {data["signingSecret"]}')
  ```
</CodeGroup>

### Comprehensive Webhook Configuration

```javascript theme={null}
const comprehensiveWebhook = {
  // Required: Endpoint URL
  url: 'https://api.example.com/webhooks/instaview',
  
  // Required: Event types to subscribe to
  events: [
    "ANALYSIS_COMPLETED",
    "ANALYSIS_FAILED",
    "PING",                    // for testing
    "INTERVIEW_COMPLETED",
    "INTERVIEW_FAILED",
    "INTERVIEW_STARTED",
    "INTERVIEW_RESCHEDULED",
    "INTERVIEW_CANCELLED"
  ],
  
  // 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);
```

<Warning>
  **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.
</Warning>

## Listing Webhooks

### List All Webhooks

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
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: ["ANALYSIS_COMPLETED", "ANALYSIS_FAILED", "PING", "INTERVIEW_COMPLETED", "INTERVIEW_FAILED", "INTERVIEW_STARTED", "INTERVIEW_RESCHEDULED", "INTERVIEW_CANCELLED"] // All interview & analysis events
});

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

### Update Custom Headers

```javascript theme={null}
// Replace all custom headers
await updateWebhook('webhook-uuid', {
  headers: [
    { name: 'Authorization', value: 'Bearer new-token' },
    { name: 'X-Environment', value: 'production' }
  ]
});
```

<Info>
  When updating headers, the entire headers array is replaced. Include all
  headers you want to keep.
</Info>

## Event Types

### Event Type Reference

Use the **constant** when registering a webhook (the `events` array). The **label** is what appears in the `event` field of the payload you receive.

| Constant                | Payload label           | Description                                  | Payload                                                       |
| ----------------------- | ----------------------- | -------------------------------------------- | ------------------------------------------------------------- |
| `ANALYSIS_COMPLETED`    | `analysis.completed`    | Analysis finished successfully               | Interview, call attempt, candidate, job IDs, analysis results |
| `ANALYSIS_FAILED`       | `analysis.failed`       | Analysis processing failed                   | IDs + error message                                           |
| `PING`                  | `ping`                  | Test event for connectivity                  | Test message                                                  |
| `INTERVIEW_COMPLETED`   | `interview.completed`   | Interview finished with analysis             | Interview, call attempt, candidate, job IDs                   |
| `INTERVIEW_FAILED`      | `interview.failed`      | Technical failure during interview           | IDs + error message                                           |
| `INTERVIEW_STARTED`     | `interview.started`     | Interview session began                      | Interview, call attempt, candidate, job IDs                   |
| `INTERVIEW_RESCHEDULED` | `interview.rescheduled` | A follow-up call attempt has been scheduled  | Previous + next attempt, reason, retry counters               |
| `INTERVIEW_CANCELLED`   | `interview.cancelled`   | Interview terminated without completing      | Reason, attempts made, last call attempt ID                   |
| `SOURCING_COMPLETED`    | `sourcing.completed`    | Sourcing run finished with scored candidates | Sourcing run IDs and results                                  |
| `SOURCING_FAILED`       | `sourcing.failed`       | Sourcing run terminated with an error        | IDs + error message                                           |
| `ENRICH_COMPLETED`      | `enrich.completed`      | Enrichment run completed                     | Enrichment run IDs and results                                |
| `ENRICH_FAILED`         | `enrich.failed`         | Enrichment run terminated with an error      | IDs + error message                                           |

### Event Selection Strategy

<Tabs>
  <Tab title="Minimal">
    Subscribe only to completion events:

    ```javascript theme={null}
    {
      events: ["INTERVIEW_COMPLETED", "ANALYSIS_COMPLETED"]
    }
    ```

    **Use case**: Simple integrations that only need final results
  </Tab>

  <Tab title="Standard">
    Include failure events for error handling:

    ```javascript theme={null}
    {
      events: ["ANALYSIS_COMPLETED", "ANALYSIS_FAILED", "INTERVIEW_COMPLETED", "INTERVIEW_FAILED"]
    }
    ```

    **Use case**: Production integrations that need to handle errors
  </Tab>

  <Tab title="Comprehensive">
    All events for full visibility:

    ```javascript theme={null}
    {
      events: ["ANALYSIS_COMPLETED", "ANALYSIS_FAILED", "PING", "INTERVIEW_COMPLETED", "INTERVIEW_FAILED", "INTERVIEW_STARTED", "INTERVIEW_RESCHEDULED", "INTERVIEW_CANCELLED"] // All interview & analysis events
    }
    ```

    **Use case**: Real-time dashboards or detailed audit trails
  </Tab>
</Tabs>

## Circuit Breaker

The circuit breaker protects both systems from cascading failures:

### How It Works

<Steps>
  <Step title="Failures Accumulate">
    Each failed delivery increments `consecutiveFailures`
  </Step>

  <Step title="Circuit Opens">
    After threshold failures, `circuitOpenedAt` is set and deliveries stop
  </Step>

  <Step title="Deliveries Paused">
    No new deliveries are attempted while circuit is open
  </Step>

  <Step title="Manual Reset">
    Use the reset endpoint to re-enable after fixing issues
  </Step>
</Steps>

### Monitoring Circuit State

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
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;
}
```

<Info>
  The webhook must be subscribed to the `ping` event (type 2) for testing to
  work.
</Info>

### Test Response Structure

```json theme={null}
{
  "success": true,
  "deliveryId": "delivery-uuid",
  "httpStatus": 200,
  "durationMs": 156,
  "message": "Webhook test successful!"
}
```

Or on failure:

```json theme={null}
{
  "success": false,
  "message": "Webhook test failed: Connection timeout"
}
```

## Deleting Webhooks

### Delete a Webhook

```javascript theme={null}
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');
  }
}
```

<Warning>
  Deleting a webhook immediately stops all deliveries. Pending deliveries will
  not be sent.
</Warning>

## Custom Headers

### Supported Headers

You can add custom headers for authentication or routing:

```javascript theme={null}
{
  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

<ResponseField name="url" type="string" required>
  The HTTPS endpoint URL to receive webhook notifications (max 2048 characters)
</ResponseField>

<ResponseField name="events" type="array" required>
  Array of event type constants to subscribe to (see Event Type Reference)
</ResponseField>

<ResponseField name="name" type="string">
  Human-readable name for the webhook (max 255 characters)
</ResponseField>

<ResponseField name="description" type="string">
  Description of the webhook's purpose
</ResponseField>

<ResponseField name="headers" type="array">
  Custom headers to include in requests (max 50 headers)
</ResponseField>

<ResponseField name="isActive" type="boolean">
  Whether the webhook is active (default: true, can be updated)
</ResponseField>

## Response Fields

<ResponseField name="id" type="string">
  Unique identifier for the webhook configuration
</ResponseField>

<ResponseField name="apiKeyId" type="string">
  The API key ID this webhook belongs to
</ResponseField>

<ResponseField name="companyId" type="string">
  Company ID this webhook is associated with. Null if webhook receives events for all companies (global webhook).
</ResponseField>

<ResponseField name="maxRetries" type="number">
  Maximum retry attempts for failed deliveries (system-configured)
</ResponseField>

<ResponseField name="timeoutMs" type="number">
  Request timeout in milliseconds (system-configured, default 30000)
</ResponseField>

<ResponseField name="consecutiveFailures" type="number">
  Current count of consecutive delivery failures
</ResponseField>

<ResponseField name="circuitOpenedAt" type="string">
  ISO 8601 timestamp when circuit breaker was triggered (null if closed)
</ResponseField>

<ResponseField name="signingSecret" type="string">
  HMAC signing secret (only returned on creation, hex-encoded)
</ResponseField>

## Error Scenarios

<AccordionGroup>
  <Accordion title="Invalid URL" icon="link-slash">
    ```json theme={null}
    {
      "error": {
        "code": "VALIDATION_ERROR",
        "message": "url must be a valid URL",
        "details": {
          "field": "url"
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Invalid Event Type" icon="calendar-xmark">
    ```json theme={null}
    {
      "error": {
        "code": "VALIDATION_ERROR",
        "message": "Invalid event type",
        "details": {
          "field": "events",
          "provided": ["INTERVIEW_UPDATED"],
          "allowed": [
            "ANALYSIS_COMPLETED",
            "ANALYSIS_FAILED",
            "PING",
            "INTERVIEW_COMPLETED",
            "INTERVIEW_FAILED",
            "INTERVIEW_STARTED",
            "INTERVIEW_RESCHEDULED",
            "INTERVIEW_CANCELLED",
            "SOURCING_COMPLETED",
            "SOURCING_FAILED",
            "ENRICH_COMPLETED",
            "ENRICH_FAILED"
          ]
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Webhook Not Found" icon="magnifying-glass">
    ```json theme={null}
    {
      "error": {
        "code": "RESOURCE_NOT_FOUND",
        "message": "Webhook configuration not found",
        "details": {
          "resourceType": "WebhookConfig",
          "resourceId": "invalid-uuid"
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Access Denied" icon="ban">
    ```json theme={null}
    {
      "error": {
        "code": "RESOURCE_ACCESS_DENIED",
        "message": "Access denied to this webhook configuration"
      }
    }
    ```
  </Accordion>

  <Accordion title="Test Requires Ping Event" icon="bell-slash">
    ```json theme={null}
    {
      "success": false,
      "message": "Webhook is not subscribed to ping events. Add the PING event type to the webhook configuration."
    }
    ```
  </Accordion>
</AccordionGroup>

## Common Patterns

### Webhook Setup Flow

```javascript theme={null}
async function setupWebhook(config) {
  // 1. Create webhook
  const response = await createWebhook({
    url: config.url,
    name: config.name,
    events: ["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: ["INTERVIEW_COMPLETED", "ANALYSIS_COMPLETED"]
  });
  
  return response;
}
```

### Health Check Automation

```javascript theme={null}
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

<CardGroup cols={2}>
  <Card title="Use Descriptive Names" icon="tag">
    Name webhooks clearly (e.g., "Production ATS Sync", "Staging Test")
  </Card>

  <Card title="Monitor Failures" icon="chart-line">
    Set up alerts for `consecutiveFailures > 0`
  </Card>

  <Card title="Test Before Production" icon="flask">
    Always test webhooks with ping before relying on them
  </Card>

  <Card title="Secure Secrets" icon="vault">
    Use a secrets manager for signing secrets
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Webhooks Guide" icon="webhook" href="/guides/webhooks">
    Learn about webhook payloads and signature verification
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/webhooks/list-webhooks">
    Complete webhook endpoint documentation
  </Card>

  <Card title="Interviews" icon="microphone" href="/guides/resources/interviews">
    Understand interview events
  </Card>

  <Card title="Scopes" icon="shield-halved" href="/guides/scopes-and-permissions">
    Configure webhook permissions
  </Card>
</CardGroup>
