Skip to Content
Docs are evolving — expect frequent updates.
Webhooks

Webhooks

Receive real-time notifications when key events occur in your Kruncher account. Configure your webhook URL and choose which events you want to be notified about.

Overview

Webhooks allow your application to be notified immediately when:

  • An analysis finishes processing
  • A project’s status or stage changes

Instead of polling the API repeatedly, webhooks push event notifications directly to your server.

Available Events

analysisCompleted

Triggered when an analysis finishes processing and results are available.

Payload:

CODE
{
  "event": "analysisCompleted",
  "data": {
    "projectId": "521a93a6-091d-4943-ba13-7c1a654a14ae",
    "analysisId": "f8e7d6c5-b4a3-2f1e-0d9c-8b7a6f5e4d3c",
    "projectName": "Tesla Motors",
    "kruncherEntityCompanyId": "04cc17f7-57d9-47ad-ba61-11912af9c027"
  }
}

When to use: Automatically fetch and process analysis results when they’re ready.

changedStatus

Triggered when a project’s status or investment stage changes.

Payload:

CODE
{
  "event": "changedStatus",
  "data": {
    "projectId": "521a93a6-091d-4943-ba13-7c1a654a14ae",
    "projectName": "Tesla Motors",
    "previousStatus": "screening",
    "newStatus": "diligence",
    "stage": "Series B",
    "changedAt": "2024-01-15T10:30:00Z"
  }
}

When to use: Update your internal systems when projects move to new stages.

Getting Started

1. Create a Webhook Endpoint

Your endpoint must:

  • Accept POST requests
  • Be accessible from the internet (HTTPS required in production)
  • Return HTTP 200 within 30 seconds
  • Validate webhook authenticity

Node.js Example

CODE
const express = require('express');
const app = express();
app.use(express.json());
 
// Webhook endpoint
app.post('/webhooks/kruncher', (req, res) => {
  const { event, data } = req.body;
  
  console.log(`Received webhook: ${event}`);
  console.log('Data:', data);
  
  // Handle the event
  if (event === 'analysisCompleted') {
    handleAnalysisCompleted(data);
  } else if (event === 'changedStatus') {
    handleStatusChanged(data);
  }
  
  // Acknowledge receipt
  res.status(200).json({ success: true });
});
 
function handleAnalysisCompleted(data) {
  console.log(`Analysis ${data.analysisId} completed for ${data.projectName}`);
  // Fetch full analysis results and process
}
 
function handleStatusChanged(data) {
  console.log(`${data.projectName} status changed to ${data.newStatus}`);
  // Update your system
}
 
app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Python Example

CODE
from flask import Flask, request, jsonify
import logging
 
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
 
@app.route('/webhooks/kruncher', methods=['POST'])
def handle_webhook():
    data = request.json
    event = data.get('event')
    event_data = data.get('data')
    
    logging.info(f"Received webhook: {event}")
    logging.info(f"Data: {event_data}")
    
    # Handle events
    if event == 'analysisCompleted':
        handle_analysis_completed(event_data)
    elif event == 'changedStatus':
        handle_status_changed(event_data)
    
    # Acknowledge receipt
    return jsonify({'success': True}), 200
 
def handle_analysis_completed(data):
    project_id = data['projectId']
    analysis_id = data['analysisId']
    project_name = data['projectName']
    
    logging.info(f"Analysis {analysis_id} completed for {project_name}")
    # Fetch full analysis and process
 
def handle_status_changed(data):
    project_name = data['projectName']
    new_status = data['newStatus']
    
    logging.info(f"{project_name} status changed to {new_status}")
    # Update system
 
if __name__ == '__main__':
    app.run(port=5000)

2. Register Your Webhook URL

Use the webhook configuration endpoint to register your webhook.

Webhook Configuration

Get Current Configuration

GET https://api.kruncher.ai/api/integration/webhook

Returns your current webhook configuration.

CODE
curl -X GET "https://api.kruncher.ai/api/integration/webhook" \
  -H "Authorization: YOUR_API_KEY_HERE"

Response:

CODE
{
  "success": true,
  "data": {
    "url": "https://your-server.com/webhooks/kruncher",
    "analysisCompleted": true,
    "changedStatus": true,
    "isActive": true,
    "createdAt": "2024-01-10T14:30:00Z",
    "updatedAt": "2024-01-15T10:30:00Z",
    "lastDelivery": "2024-01-15T09:45:00Z"
  }
}

Create/Update Webhook Configuration

POST/PUT https://api.kruncher.ai/api/integration/webhook

Create a new webhook or update existing configuration.

cURL

CODE
curl -X POST "https://api.kruncher.ai/api/integration/webhook" \
  -H "Authorization: YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/kruncher",
    "analysisCompleted": true,
    "changedStatus": true
  }'

JavaScript

CODE
const API_KEY = "YOUR_API_KEY_HERE";
const BASE_URL = "https://api.kruncher.ai/api";
 
async function createWebhook(webhookUrl, events) {
  const payload = {
    url: webhookUrl,
    analysisCompleted: events.includes('analysisCompleted'),
    changedStatus: events.includes('changedStatus')
  };
 
  const response = await fetch(`${BASE_URL}/integration/webhook`, {
    method: "POST",
    headers: {
      "Authorization": `${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify(payload)
  });
 
  if (!response.ok) {
    throw new Error(`Failed to create webhook: ${response.statusText}`);
  }
 
  return await response.json();
}
 
// Usage
try {
  const result = await createWebhook(
    "https://your-server.com/webhooks/kruncher",
    ["analysisCompleted", "changedStatus"]
  );
  console.log("✓ Webhook created:", result.data);
} catch (error) {
  console.error("✗ Error:", error.message);
}

Python

CODE
import requests
 
API_KEY = "YOUR_API_KEY_HERE"
BASE_URL = "https://api.kruncher.ai/api"
 
def create_webhook(webhook_url, events):
    """Create or update webhook configuration."""
    
    headers = {
        "Authorization": f"{API_KEY}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "url": webhook_url,
        "analysisCompleted": "analysisCompleted" in events,
        "changedStatus": "changedStatus" in events
    }
    
    response = requests.post(
        f"{BASE_URL}/integration/webhook",
        json=payload,
        headers=headers
    )
    response.raise_for_status()
    
    return response.json()
 
# Usage
try:
    result = create_webhook(
        "https://your-server.com/webhooks/kruncher",
        ["analysisCompleted", "changedStatus"]
    )
    print(f"✓ Webhook created: {result['data']}")
except Exception as error:
    print(f"✗ Error: {error}")

Request Body Fields

FieldTypeRequiredDescription
urlstringYesYour webhook endpoint (must be HTTPS in production)
analysisCompletedbooleanNoReceive analysis completion notifications (default: false)
changedStatusbooleanNoReceive project status change notifications (default: false)

Response

CODE
{
  "success": true,
  "data": {
    "id": "webhook_abc123",
    "url": "https://your-server.com/webhooks/kruncher",
    "analysisCompleted": true,
    "changedStatus": true,
    "isActive": true,
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

Fetching Full Data

Webhook payloads contain only essential IDs. Use your API key to fetch complete details:

Get Project Details

CODE
curl -X GET "https://api.kruncher.ai/api/company/retrieve?projectId=521a93a6-091d-4943-ba13-7c1a654a14ae" \
  -H "Authorization: YOUR_API_KEY_HERE"

Get Analysis Details

CODE
curl -X GET "https://api.kruncher.ai/api/analysis?analysisId=f8e7d6c5-b4a3-2f1e-0d9c-8b7a6f5e4d3c" \
  -H "Authorization: YOUR_API_KEY_HERE"

Complete Event Handler

CODE
class WebhookHandler {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = "https://api.kruncher.ai/api";
  }
 
  async getProjectDetails(projectId) {
    const response = await fetch(
      `${this.baseUrl}/company/retrieve?projectId=${projectId}`,
      {
        headers: { "Authorization": `${this.apiKey}` }
      }
    );
    return await response.json();
  }
 
  async getAnalysisDetails(analysisId) {
    const response = await fetch(
      `${this.baseUrl}/analysis?analysisId=${analysisId}`,
      {
        headers: { "Authorization": `${this.apiKey}` }
      }
    );
    return await response.json();
  }
 
  async handleAnalysisCompleted(data) {
    const { projectId, analysisId } = data;
 
    // Fetch full details
    const [project, analysis] = await Promise.all([
      this.getProjectDetails(projectId),
      this.getAnalysisDetails(analysisId)
    ]);
 
    console.log("Project:", project.data);
    console.log("Analysis:", analysis.data);
 
    // Process analysis results
    this.processAnalysis(project.data, analysis.data);
  }
 
  async handleStatusChanged(data) {
    const { projectId, newStatus } = data;
 
    // Fetch updated project details
    const project = await this.getProjectDetails(projectId);
 
    console.log(`Project ${project.data.name} now in ${newStatus} stage`);
 
    // Update your system
    this.updateSystem(project.data);
  }
 
  processAnalysis(project, analysis) {
    // Your custom processing logic
    console.log(`Processing analysis for ${project.name}`);
  }
 
  updateSystem(project) {
    // Your custom update logic
    console.log(`Updating system for ${project.name}`);
  }
}
 
// Usage
const handler = new WebhookHandler("YOUR_API_KEY_HERE");
 
app.post('/webhooks/kruncher', async (req, res) => {
  const { event, data } = req.body;
 
  try {
    if (event === 'analysisCompleted') {
      await handler.handleAnalysisCompleted(data);
    } else if (event === 'changedStatus') {
      await handler.handleStatusChanged(data);
    }
 
    res.status(200).json({ success: true });
  } catch (error) {
    console.error("Webhook processing error:", error);
    res.status(500).json({ success: false, error: error.message });
  }
});

Webhook History

View webhook delivery history for debugging and monitoring.

Get Webhook History

GET https://api.kruncher.ai/api/integration/webhook/history

Query Parameters:

ParameterTypeDescription
limitintegerNumber of records (default: 50, max: 100)
offsetintegerPagination offset (default: 0)
eventstringFilter by event type
statusstringFilter by delivery status

cURL

CODE
curl -X GET "https://api.kruncher.ai/api/integration/webhook/history?limit=20" \
  -H "Authorization: YOUR_API_KEY_HERE"

JavaScript

CODE
async function getWebhookHistory(limit = 50, offset = 0) {
  const params = new URLSearchParams({ limit, offset });
  
  const response = await fetch(
    `${BASE_URL}/integration/webhook/history?${params}`,
    {
      headers: { "Authorization": `${API_KEY}` }
    }
  );
  
  return await response.json();
}
 
// Usage
const history = await getWebhookHistory(20);
console.log("Recent deliveries:", history.data);

Python

CODE
def get_webhook_history(limit=50, offset=0, event=None, status=None):
    """Get webhook delivery history."""
    
    params = {
        "limit": limit,
        "offset": offset
    }
    
    if event:
        params["event"] = event
    if status:
        params["status"] = status
    
    response = requests.get(
        f"{BASE_URL}/integration/webhook/history",
        params=params,
        headers={"Authorization": f"{API_KEY}"}
    )
    response.raise_for_status()
    
    return response.json()
 
# Usage
history = get_webhook_history(limit=20, status="Success")
for delivery in history['data']:
    print(f"{delivery['event']}: {delivery['status']}")

Response Structure

CODE
{
  "success": true,
  "data": [
    {
      "id": "delivery_abc123",
      "event": "analysisCompleted",
      "status": "Success",
      "statusDescription": null,
      "payload": {
        "projectId": "521a93a6-091d-4943-ba13-7c1a654a14ae",
        "analysisId": "f8e7d6c5-b4a3-2f1e-0d9c-8b7a6f5e4d3c",
        "projectName": "Tesla Motors"
      },
      "httpStatus": 200,
      "createdAt": "2024-01-15T10:30:00Z",
      "deliveredAt": "2024-01-15T10:30:02Z"
    },
    {
      "id": "delivery_def456",
      "event": "changedStatus",
      "status": "Error Response",
      "statusDescription": "Connection timeout after 30 seconds",
      "payload": {
        "projectId": "631b84b7-192e-4a54-ca24-8d765b725b95",
        "newStatus": "diligence"
      },
      "httpStatus": null,
      "createdAt": "2024-01-15T09:45:00Z",
      "deliveredAt": "2024-01-15T09:45:32Z"
    }
  ],
  "pagination": {
    "total": 156,
    "limit": 20,
    "offset": 0
  }
}

History Fields

FieldTypeDescription
idstringUnique delivery ID
eventstringEvent type
statusstring”Success”, “Error Request”, or “Error Response”
statusDescriptionstringError details if applicable
payloadobjectThe webhook payload sent
httpStatusintegerHTTP response code from your endpoint
createdAtstringWhen event was triggered
deliveredAtstringWhen delivery was attempted

Best Practices

Webhook Security

  1. Validate HTTPS

    • Require HTTPS in production
    • Use valid SSL certificates
    • Never use self-signed certs in production
  2. Verify Authenticity

    CODE
    // Check webhook comes from Kruncher
    const apiKey = req.headers['x-kruncher-api-key'];
    if (apiKey !== process.env.KRUNCHER_API_KEY) {
      return res.status(401).json({ error: 'Unauthorized' });
    }
  3. Handle Duplicates

    CODE
    // Store delivery IDs to prevent duplicate processing
    const processedDeliveries = new Set();
     
    app.post('/webhooks/kruncher', (req, res) => {
      const deliveryId = req.headers['x-delivery-id'];
      
      if (processedDeliveries.has(deliveryId)) {
        return res.status(200).json({ success: true }); // Already processed
      }
      
      // Process webhook
      processedDeliveries.add(deliveryId);
    });

Reliability

  1. Quick Response

    • Acknowledge receipt immediately (200 OK)
    • Process asynchronously in background
    • Don’t perform long operations synchronously
  2. Error Handling

    CODE
    app.post('/webhooks/kruncher', async (req, res) => {
      // Always respond quickly
      res.status(200).json({ success: true });
      
      // Process asynchronously
      setImmediate(async () => {
        try {
          await processWebhook(req.body);
        } catch (error) {
          console.error('Webhook processing failed:', error);
          // Implement retry logic or alerting
        }
      });
    });
  3. Monitoring

    • Log all webhook deliveries
    • Monitor delivery failures
    • Set up alerts for repeated failures
    • Check webhook history regularly

Performance

  1. Batch Processing

    CODE
    from collections import defaultdict
     
    pending_tasks = defaultdict(list)
     
    def handle_webhook(event, data):
        # Group related tasks
        project_id = data['projectId']
        pending_tasks[project_id].append({
            'event': event,
            'data': data
        })
        
        # Process after delay to batch
        schedule_batch_processing(project_id, delay=5)
  2. Caching

    • Cache project/analysis details locally
    • Reduce API calls to fetch full data
    • Refresh cache on status changes

Common Patterns

Pattern 1: Auto-Process Analysis Results

CODE
async function handleAnalysisComplete(data) {
  const handler = new WebhookHandler(API_KEY);
  
  // Get full analysis
  const analysis = await handler.getAnalysisDetails(data.analysisId);
  
  // Process results
  updateDatabase({
    projectId: data.projectId,
    analysisResults: analysis.data,
    processedAt: new Date()
  });
  
  // Notify team
  sendSlackNotification(
    `Analysis complete for ${data.projectName}`,
    analysis.data.summary
  );
}

Pattern 2: Update External Systems

CODE
def handle_status_change(data):
    """Sync status changes to external CRM."""
    
    project_id = data['projectId']
    new_status = data['newStatus']
    
    # Map Kruncher status to external CRM status
    crm_status = map_status(new_status)
    
    # Update CRM
    crm_client.update_deal(
        deal_id=project_id,
        status=crm_status
    )
    
    # Log change
    logger.info(f"Updated {project_id} to {crm_status}")

Pattern 3: Delayed Processing

CODE
class WebhookQueue {
  constructor() {
    this.queue = [];
  }
 
  async add(event, data) {
    this.queue.push({ event, data, timestamp: Date.now() });
    
    // Process queue after batch window
    if (this.queue.length === 1) {
      setTimeout(() => this.processBatch(), 5000);
    }
  }
 
  async processBatch() {
    const batch = this.queue.splice(0);
    
    for (const item of batch) {
      await this.handleEvent(item.event, item.data);
    }
  }
}

Error Handling

Handling Delivery Failures

CODE
async function getFailedDeliveries() {
  const response = await fetch(
    `${BASE_URL}/integration/webhook/history?status=Error%20Response`,
    { headers: { "Authorization": `${API_KEY}` } }
  );
  
  const history = await response.json();
  
  // Check for pattern of failures
  const failures = history.data;
  if (failures.length > 5) {
    console.error("⚠️  Multiple webhook failures detected");
    console.error("Check your endpoint accessibility");
  }
  
  return failures;
}

Retry Logic

CODE
def handle_webhook_with_retry(event, data, max_retries=3):
    """Process webhook with retry logic."""
    
    for attempt in range(max_retries):
        try:
            if event == 'analysisCompleted':
                process_analysis(data)
            elif event == 'changedStatus':
                process_status_change(data)
            
            return True  # Success
        
        except Exception as error:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # Exponential backoff
                time.sleep(wait_time)
            else:
                log_failure(event, data, error)
                raise

Testing Your Webhook

Local Testing with ngrok

CODE
# Start your local server
npm start  # or python app.py
 
# Expose to internet
ngrok http 3000
 
# Register webhook with ngrok URL
# https://abc123.ngrok.io/webhooks/kruncher
 
# View requests
ngrok web  # Opens debugging interface

Manual Testing

CODE
# Send test webhook
curl -X POST "https://your-server.com/webhooks/kruncher" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "analysisCompleted",
    "data": {
      "projectId": "test-project-id",
      "analysisId": "test-analysis-id",
      "projectName": "Test Project"
    }
  }'

Troubleshooting

”Connection timeout” Error

  • Check endpoint is publicly accessible
  • Verify firewall allows incoming connections
  • Ensure endpoint returns response within 30 seconds
  • Check server logs for errors

Webhook Not Triggering

  • Verify webhook is active in settings
  • Check event subscription is enabled
  • Verify webhook URL is correct
  • Check webhook history for errors

Duplicate Events

  • Implement idempotency (check delivery ID)
  • Store processed event IDs
  • Handle gracefully on retry

Need Help?

  • Check webhook history for delivery status and errors
  • Verify endpoint is accessible and returning 200 OK
  • Test manually with curl to debug issues
  • Use ngrok for local development and testing
Last updated on