Skip to Content
Docs are evolving — expect frequent updates.
Webhook Explanation

Webhooks

Receive real-time notifications when key events occur in your Kruncher account. Register up to 5 webhook URLs and choose, for each one, which events it receives.

Overview

Webhooks allow your application to be notified immediately when:

  • A company analysis finishes processing
  • A fund data-room 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

Company analysis completed. Triggered when a company report finishes processing and its results are available. (The event code stays analysisCompleted for backwards compatibility.)

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 company 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",
    "analysisId": "f8e7d6c5-b4a3-2f1e-0d9c-8b7a6f5e4d3c",
    "newStatusId": "9c1f2a3b-4d5e-4f60-8a71-b2c3d4e5f607",
    "newStatusname": "Diligence",
    "kruncherEntityCompanyId": "04cc17f7-57d9-47ad-ba61-11912af9c027",
    "mappings": [
      { "externalId": "crm-4821", "source": "affinity" }
    ],
    "projectName": "Tesla Motors"
  }
}
FieldTypeDescription
projectIdstringThe project whose status changed
analysisIdstring/nullIts latest analysis
newStatusIdstringId of the new status
newStatusnamestringName of the new status
kruncherEntityCompanyIdstring/nullThe Kruncher entity behind the project
mappingsarrayYour external-system mappings for that entity
projectNamestringProject name

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

fundAnalysisCompleted

Fund analysis completed. Triggered when a fund data-room analysis finishes processing — that is, when the run reaches status: completed. Runs that fail are not sent; poll Fund Detail if you need to see error states.

Payload:

CODE
{
  "event": "fundAnalysisCompleted",
  "data": {
    "fundExtractionId": "3f1c9a2e-7b4d-4e1a-9c2f-5d6e7f8a9b0c",
    "fundId": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b",
    "fundName": "Northwind Growth Fund II",
    "status": "completed",
    "refDate": "2026-06-30",
    "fundstage": {
      "id": "1a2b3c4d-0000-4000-8000-000000000001",
      "name": "Screening",
      "type": "screening"
    },
    "companiesCount": 14
  }
}
FieldTypeDescription
fundExtractionIdstringThe completed run. Pass it to every fund read endpoint
fundIdstringThe fund the run belongs to
fundNamestringFund name
statusstringAlways completed
refDatestring/nullReporting date of the documents (YYYY-MM-DD)
fundstageobject/nullThe fund’s stage: id, name, type
companiesCountintegerPortfolio companies found in the data room

When to use: Pull the fund once it is ready. Fetch the details with:

CODE
curl -X GET "https://api.kruncher.ai/api/integrationfund/fund?fundExtractionId=3f1c9a2e-7b4d-4e1a-9c2f-5d6e7f8a9b0c" \
  -H "Authorization: YOUR_API_KEY_HERE"

See Funds for the full fund API.

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
  switch (event) {
    case 'analysisCompleted':
      handleAnalysisCompleted(data);
      break;
    case 'changedStatus':
      handleStatusChanged(data);
      break;
    case 'fundAnalysisCompleted':
      handleFundAnalysisCompleted(data);
      break;
  }
 
  // Acknowledge receipt
  res.status(200).json({ success: true });
});
 
function handleAnalysisCompleted(data) {
  console.log(`Company analysis ${data.analysisId} completed for ${data.projectName}`);
  // Fetch full analysis results and process
}
 
function handleStatusChanged(data) {
  console.log(`${data.projectName} status changed to ${data.newStatusname}`);
  // Update your system
}
 
function handleFundAnalysisCompleted(data) {
  console.log(`Fund ${data.fundName} analysed: ${data.companiesCount} companies at ${data.refDate}`);
  // Fetch the run with GET /api/integrationfund/fund?fundExtractionId=...
}
 
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)
    elif event == 'fundAnalysisCompleted':
        handle_fund_analysis_completed(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"Company analysis {analysis_id} completed for {project_name}")
    # Fetch full analysis and process
 
def handle_status_changed(data):
    project_name = data['projectName']
    new_status = data['newStatusname']
 
    logging.info(f"{project_name} status changed to {new_status}")
    # Update system
 
def handle_fund_analysis_completed(data):
    fund_extraction_id = data['fundExtractionId']
    fund_name = data['fundName']
 
    logging.info(f"Fund {fund_name} analysed ({data['companiesCount']} companies)")
    # Fetch with GET /api/integrationfund/fund?fundExtractionId=<fund_extraction_id>
 
if __name__ == '__main__':
    app.run(port=5000)

2. Register Your Webhook URL

Use the webhook configuration endpoint to register your webhook.

Webhook Configuration

The simplest way to register a webhook is in the Kruncher app: Settings → Integrations. Set the URL, tick the events you want, and save.

Multiple webhooks

A workspace can register up to 5 webhooks. Each has its own URL and its own set of events, and every webhook subscribed to an event receives it. Typical setups:

  • one URL for company events (analysisCompleted, changedStatus) and another for fund events (fundAnalysisCompleted);
  • the same event sent to several systems — for example your CRM and a data warehouse.

Every delivery of an event carries the same body, whichever webhook receives it. A URL can be registered only once — to change which events it receives, edit that webhook.

MethodEndpointDescription
GET/api/integration/webhooksList your webhooks, oldest first, with maxWebhooks
POST/api/integration/webhooksAdd a webhook. Returns Error215 once you have 5, Error217 if the URL is already registered
PUT/api/integration/webhooks/{id}Update one webhook; send only the fields you change
DELETE/api/integration/webhooks/{id}Delete one webhook
CODE
curl -X POST "https://api.kruncher.ai/api/integration/webhooks" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/kruncher-funds",
    "fundAnalysisCompleted": true
  }'

List response:

CODE
{
  "metadata": { "code": "1000", "title": "Successful", "description": "" },
  "data": {
    "maxWebhooks": 5,
    "webhooks": [
      {
        "id": "6f0c1d2e-...",
        "url": "https://your-server.com/webhooks/kruncher",
        "analysisCompleted": true,
        "analysisChangedStatus": true,
        "fundAnalysisCompleted": false
      },
      {
        "id": "9a8b7c6d-...",
        "url": "https://your-server.com/webhooks/kruncher-funds",
        "analysisCompleted": false,
        "analysisChangedStatus": false,
        "fundAnalysisCompleted": true
      }
    ]
  }
}

A PUT or DELETE on an id that is not one of your webhooks returns Error216.

The single-webhook endpoints below (/api/integration/webhook) keep working exactly as before for one webhook: GET and PUT read and update your oldest webhook, and POST creates it if you have none or updates it if you do. Use /api/integration/webhooks to add a second one.

These configuration endpoints do not accept an Integration API key. Unlike the rest of the Integration API, /api/integration/webhooks, /api/integration/webhook and /api/integration/webhook/history require a signed-in Kruncher session — an Auth0 access token sent as Authorization: Bearer <token>, the same token the web app uses. An API key returns 401. Everything else on this page — the delivery payloads and the endpoints you call to fetch full data — works with your API key as normal.

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: Bearer YOUR_ACCESS_TOKEN"

Response:

CODE
{
  "metadata": {
    "code": "1000",
    "title": "Successful",
    "description": ""
  },
  "data": {
    "id": "webhook_abc123",
    "url": "https://your-server.com/webhooks/kruncher",
    "analysisCompleted": true,
    "analysisChangedStatus": true,
    "fundAnalysisCompleted": true,
    "createdAt": "2024-01-10T14:30:00Z",
    "updatedAt": "2024-01-15T10:30: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: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/kruncher",
    "analysisCompleted": true,
    "analysisChangedStatus": true,
    "fundAnalysisCompleted": true
  }'

JavaScript

CODE
const API_KEY = "YOUR_API_KEY_HERE";
const BASE_URL = "https://api.kruncher.ai/api";
 
// ACCESS_TOKEN is an Auth0 access token from a signed-in session,
// not an Integration API key — see the note above.
async function createWebhook(webhookUrl, events, accessToken) {
  const payload = {
    url: webhookUrl,
    analysisCompleted: events.includes('analysisCompleted'),
    analysisChangedStatus: events.includes('changedStatus'),
    fundAnalysisCompleted: events.includes('fundAnalysisCompleted')
  };
 
  const response = await fetch(`${BASE_URL}/integration/webhook`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${accessToken}`,
      "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", "fundAnalysisCompleted"],
    ACCESS_TOKEN
  );
  console.log("✓ Webhook created:", result.data);
} catch (error) {
  console.error("✗ Error:", error.message);
}

Python

CODE
import requests
 
# An Auth0 access token from a signed-in session, not an Integration API key.
ACCESS_TOKEN = "YOUR_ACCESS_TOKEN"
BASE_URL = "https://api.kruncher.ai/api"
 
def create_webhook(webhook_url, events):
    """Create or update webhook configuration."""
    
    headers = {
        "Authorization": f"Bearer {ACCESS_TOKEN}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "url": webhook_url,
        "analysisCompleted": "analysisCompleted" in events,
        "analysisChangedStatus": "changedStatus" in events,
        "fundAnalysisCompleted": "fundAnalysisCompleted" 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", "fundAnalysisCompleted"]
    )
    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 company analysis completion notifications (default: false)
analysisChangedStatusbooleanNoReceive project status change notifications (default: false)
fundAnalysisCompletedbooleanNoReceive fund analysis completion notifications (default: false)

The same three events are toggles in the Kruncher app under Settings → API Integration → Webhook.

Response

CODE
{
  "metadata": {
    "code": "1000",
    "title": "Successful",
    "description": ""
  },
  "data": {
    "id": "webhook_abc123",
    "url": "https://your-server.com/webhooks/kruncher",
    "analysisCompleted": true,
    "analysisChangedStatus": true,
    "fundAnalysisCompleted": 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/integration/projects?id=521a93a6-091d-4943-ba13-7c1a654a14ae" \
  -H "Authorization: YOUR_API_KEY_HERE"

Get Analysis Details

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

Get Fund Run Details

CODE
curl -X GET "https://api.kruncher.ai/api/integrationfund/fund?fundExtractionId=3f1c9a2e-7b4d-4e1a-9c2f-5d6e7f8a9b0c" \
  -H "Authorization: YOUR_API_KEY_HERE"

For the whole report in one call, use Full Report instead:

CODE
curl -X GET "https://api.kruncher.ai/api/integration/project/fullreport?projectId=521a93a6-091d-4943-ba13-7c1a654a14ae" \
  -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}/integration/projects?id=${projectId}`,
      {
        headers: { "Authorization": `${this.apiKey}` }
      }
    );
    return await response.json();
  }
 
  async getAnalysisDetails(analysisId) {
    const response = await fetch(
      `${this.baseUrl}/integration/analysis/detail?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, newStatusname } = data;
 
    // Fetch updated project details
    const project = await this.getProjectDetails(projectId);
 
    console.log(`Project ${project.data.name} now in ${newStatusname} stage`);
 
    // Update your system
    this.updateSystem(project.data);
  }
 
  async getFundDetails(fundExtractionId) {
    const response = await fetch(
      `${this.baseUrl}/integrationfund/fund?fundExtractionId=${fundExtractionId}`,
      {
        headers: { "Authorization": `${this.apiKey}` }
      }
    );
    return await response.json();
  }
 
  async handleFundAnalysisCompleted(data) {
    const { fundExtractionId } = data;
 
    // Fetch the run summary, then the view you need (workbook, overview, assets, ...)
    const fund = await this.getFundDetails(fundExtractionId);
 
    console.log("Fund:", fund.data.name, fund.data.companiesCount, "companies");
  }
 
  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);
    } else if (event === 'fundAnalysisCompleted') {
      await handler.handleFundAnalysisCompleted(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

Returns the 10 most recent webhook deliveries, ordered by most recent first. No query parameters are accepted.

cURL

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

JavaScript

CODE
async function getWebhookHistory() {
  const response = await fetch(
    `${BASE_URL}/integration/webhook/history`,
    {
      // Session token, not an Integration API key
      headers: { "Authorization": `Bearer ${ACCESS_TOKEN}` }
    }
  );
 
  return await response.json();
}
 
// Usage
const history = await getWebhookHistory();
console.log("Recent deliveries:", history.data);

Python

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

Response Structure

CODE
{
  "metadata": {
    "code": "1000",
    "title": "Successful",
    "description": ""
  },
  "data": [
    {
      "url": "https://your-server.com/webhooks/kruncher",
      "event": "analysisCompleted",
      "status": "Success",
      "statusDescription": null,
      "createdAt": "2024-01-15T10:30:00Z"
    },
    {
      "event": "changedStatus",
      "status": "Error Response",
      "statusDescription": "Connection timeout after 30 seconds",
      "createdAt": "2024-01-15T09:45:00Z"
    }
  ]
}

History Fields

FieldTypeDescription
urlstringThe URL the delivery was sent to
eventstringEvent type (analysisCompleted, changedStatus or fundAnalysisCompleted)
statusstring"Success", "Error Request", or "Error Response"
statusDescriptionstring/nullError details if applicable
createdAtstringISO 8601 timestamp of the event

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`,
    { headers: { "Authorization": `${API_KEY}` } }
  );
 
  const history = await response.json();
 
  // Check for pattern of failures
  const failures = history.data.filter(d => d.status !== 'Success');
  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