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:
{
"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:
{
"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
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
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.
curl -X GET "https://api.kruncher.ai/api/integration/webhook" \
-H "Authorization: YOUR_API_KEY_HERE"Response:
{
"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
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
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
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
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Your webhook endpoint (must be HTTPS in production) |
analysisCompleted | boolean | No | Receive analysis completion notifications (default: false) |
changedStatus | boolean | No | Receive project status change notifications (default: false) |
Response
{
"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
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
curl -X GET "https://api.kruncher.ai/api/analysis?analysisId=f8e7d6c5-b4a3-2f1e-0d9c-8b7a6f5e4d3c" \
-H "Authorization: YOUR_API_KEY_HERE"Complete Event Handler
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:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Number of records (default: 50, max: 100) |
offset | integer | Pagination offset (default: 0) |
event | string | Filter by event type |
status | string | Filter by delivery status |
cURL
curl -X GET "https://api.kruncher.ai/api/integration/webhook/history?limit=20" \
-H "Authorization: YOUR_API_KEY_HERE"JavaScript
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
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
{
"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
| Field | Type | Description |
|---|---|---|
id | string | Unique delivery ID |
event | string | Event type |
status | string | ”Success”, “Error Request”, or “Error Response” |
statusDescription | string | Error details if applicable |
payload | object | The webhook payload sent |
httpStatus | integer | HTTP response code from your endpoint |
createdAt | string | When event was triggered |
deliveredAt | string | When delivery was attempted |
Best Practices
Webhook Security
-
Validate HTTPS
- Require HTTPS in production
- Use valid SSL certificates
- Never use self-signed certs in production
-
Verify Authenticity
// 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' }); } -
Handle Duplicates
// 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
-
Quick Response
- Acknowledge receipt immediately (200 OK)
- Process asynchronously in background
- Don’t perform long operations synchronously
-
Error Handling
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 } }); }); -
Monitoring
- Log all webhook deliveries
- Monitor delivery failures
- Set up alerts for repeated failures
- Check webhook history regularly
Performance
-
Batch Processing
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) -
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
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
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
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
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
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)
raiseTesting Your Webhook
Local Testing with ngrok
# 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 interfaceManual Testing
# 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
Related Endpoints
- Get Project Details - Fetch complete project data
- Get Analysis Details - Fetch complete analysis data
- List Projects - View all projects
- Search Projects - Find specific projects
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