What Are Webhooks?
Webhooks allow you to receive real-time HTTP notifications whenever specific events occur in your Mindhunters workspace. Instead of continuously polling the API for updates, Mindhunters will send POST requests to your specified endpoint URL when events happen.
Why Use Webhooks?
Real-time Updates Get instant notifications when conversations end, status changes occur, or intents are triggered.
Reduced API Calls No need to poll the API repeatedly. Webhooks push data to you automatically.
Event-Driven Architecture Build reactive applications that respond immediately to conversation events.
Custom Integration Integrate Mindhunters with your existing systems and workflows seamlessly.
Setting Up Webhooks
Prerequisites
Before configuring webhooks, ensure you have:
Admin or developer access to your Mindhunters workspace
A publicly accessible HTTPS endpoint to receive webhook events
(Recommended) A secret key for webhook signature verification
Configuration Steps
Access Developer Settings
Log in to your Mindhunters workspace at https://your-tenant.mindhunters.ai
Click on your profile icon in the top right corner
Select “Developer” from the dropdown menu
Navigate to Webhooks
In the Developer section, find the “Webhooks” configuration area.
Enter Your Webhook URL
In the “Webhook URL” field, enter your endpoint URL
This must be a publicly accessible HTTPS URL
Example: https://api.yourcompany.com/webhooks/mihu
HTTP URLs are not supported for security reasons. Your endpoint must use HTTPS.
Add Secret Key (Recommended)
Enter a “Secret Key” in the provided field
This key will be used to sign webhook payloads
Store this key securely - you’ll need it to verify webhook signatures
Use a strong, random string (minimum 32 characters recommended)
Generate a secure secret key using: openssl rand -hex 32
Select Event Types
Choose which events you want to receive notifications for:
✅ Conversation Update : Real-time updates during conversations
✅ Conversation End Report : Detailed report when conversation completes
✅ Conversation Status : Status changes (initiated, in-progress, ended)
✅ Intent Call : Triggered when specific intents are detected
✅ Text Evaluation : Results from text-based evaluations
✅ Voice Evaluation : Results from voice conversation evaluations
Select only the events you need to reduce unnecessary webhook traffic.
Save Configuration
Click “Save” or “Update” to activate your webhook configuration.
Available Event Types
Conversation Update
Sent in real-time as the conversation progresses. Useful for live monitoring and transcription display.
Event Type: conversation.update
{
"event" : "conversation.update" ,
"timestamp" : "2025-01-15T10:30:45Z" ,
"data" : {
"id" : "550e8400-e29b-41d4-a716-446655440000" ,
"agentId" : "agent-uuid-here" ,
"status" : "in-progress" ,
"participant" : {
"number" : "+1234567890" ,
"name" : "John Doe"
},
"transcript" : [
{
"speaker" : "agent" ,
"text" : "Hello, how can I help you today?" ,
"timestamp" : "2025-01-15T10:30:10Z"
},
{
"speaker" : "participant" ,
"text" : "I need help with my account." ,
"timestamp" : "2025-01-15T10:30:35Z"
}
],
"currentSentiment" : "neutral"
}
}
Conversation End Report
Sent when a conversation completes. Contains comprehensive conversation analytics.
Event Type: conversation.end
{
"event" : "conversation.end" ,
"timestamp" : "2025-01-15T10:35:00Z" ,
"data" : {
"id" : "550e8400-e29b-41d4-a716-446655440000" ,
"agentId" : "agent-uuid-here" ,
"status" : "completed" ,
"duration" : 285 ,
"participant" : {
"number" : "+1234567890" ,
"name" : "John Doe"
},
"summary" : {
"outcome" : "resolved" ,
"sentiment" : "positive" ,
"intentsDetected" : [ "account_inquiry" , "billing_question" ],
"resolutionTime" : 285 ,
"customerSatisfaction" : 4.5
},
"fullTranscript" : "..." ,
"recording" : {
"url" : "https://recordings.mindhunters.ai/..." ,
"duration" : 285
},
"metadata" : {
"campaign" : "customer-support" ,
"customerId" : "12345"
}
}
}
Conversation Status
Sent when conversation status changes (initiated → in-progress → ended/failed/missed).
Event Type: conversation.status
{
"event" : "conversation.status" ,
"timestamp" : "2025-01-15T10:30:15Z" ,
"data" : {
"id" : "550e8400-e29b-41d4-a716-446655440000" ,
"agentId" : "agent-uuid-here" ,
"previousStatus" : "initiated" ,
"currentStatus" : "in-progress" ,
"participant" : {
"number" : "+1234567890"
},
"statusChangedAt" : "2025-01-15T10:30:15Z"
}
}
Intent Call
Triggered when a specific intent is detected during the conversation. Useful for triggering actions or integrations.
Event Type: intent.triggered
{
"event" : "intent.triggered" ,
"timestamp" : "2025-01-15T10:32:00Z" ,
"data" : {
"conversationId" : "550e8400-e29b-41d4-a716-446655440000" ,
"intent" : {
"name" : "transfer_to_human" ,
"confidence" : 0.95 ,
"parameters" : {
"department" : "billing" ,
"priority" : "high"
}
},
"context" : {
"lastUserMessage" : "I need to speak with someone about my bill" ,
"conversationTurn" : 5
}
}
}
Text Evaluation
Results from text-based conversation evaluations.
Event Type: evaluation.text
{
"event" : "evaluation.text" ,
"timestamp" : "2025-01-15T10:35:30Z" ,
"data" : {
"conversationId" : "550e8400-e29b-41d4-a716-446655440000" ,
"evaluationType" : "quality_assurance" ,
"scores" : {
"accuracy" : 0.92 ,
"relevance" : 0.88 ,
"completeness" : 0.95
},
"feedback" : "Conversation handled professionally with accurate information." ,
"recommendations" : [
"Consider offering callback option earlier"
]
}
}
Voice Evaluation
Results from voice conversation quality evaluations.
Event Type: evaluation.voice
{
"event" : "evaluation.voice" ,
"timestamp" : "2025-01-15T10:35:45Z" ,
"data" : {
"conversationId" : "550e8400-e29b-41d4-a716-446655440000" ,
"evaluationType" : "voice_quality" ,
"scores" : {
"clarity" : 0.94 ,
"naturalness" : 0.89 ,
"emotionalTone" : 0.87 ,
"audioQuality" : 0.96
},
"metrics" : {
"averageResponseTime" : 1.2 ,
"silenceDuration" : 5.3 ,
"interruptionCount" : 2
}
}
}
Webhook Payload Structure
All webhook payloads follow a consistent structure:
{
"event" : "event.type" ,
"timestamp" : "ISO 8601 timestamp" ,
"data" : {
// Event-specific data
},
"signature" : "webhook-signature-hash"
}
The type of event that triggered the webhook
ISO 8601 formatted timestamp when the event occurred
Event-specific payload containing relevant information
HMAC-SHA256 signature for payload verification (when secret key is configured)
Security & Verification
Verifying Webhook Signatures
When you configure a secret key, Mindhunters signs each webhook payload with an HMAC-SHA256 signature. Always verify this signature to ensure the webhook is genuine.
const crypto = require ( 'crypto' );
function verifyWebhookSignature ( payload , signature , secret ) {
const hmac = crypto . createHmac ( 'sha256' , secret );
const expectedSignature = hmac . update ( JSON . stringify ( payload )). digest ( 'hex' );
return crypto . timingSafeEqual (
Buffer . from ( signature ),
Buffer . from ( expectedSignature )
);
}
// Express.js middleware example
app . post ( '/webhooks/mihu' , express . json (), ( req , res ) => {
const signature = req . headers [ 'x-mihu-signature' ];
const secret = process . env . MIHU_WEBHOOK_SECRET ;
if ( ! verifyWebhookSignature ( req . body , signature , secret )) {
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
// Process webhook
const { event , data } = req . body ;
console . log ( `Received event: ${ event } ` );
res . status ( 200 ). json ({ received: true });
});
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = Flask( __name__ )
def verify_webhook_signature ( payload , signature , secret ):
expected_signature = hmac.new(
secret.encode( 'utf-8' ),
json.dumps(payload).encode( 'utf-8' ),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@app.route ( '/webhooks/mihu' , methods = [ 'POST' ])
def handle_mihu_webhook ():
signature = request.headers.get( 'X-Mindhunters-Signature' )
secret = os.environ.get( 'MIHU_WEBHOOK_SECRET' )
payload = request.json
if not verify_webhook_signature(payload, signature, secret):
return jsonify({ 'error' : 'Invalid signature' }), 401
# Process webhook
event_type = payload.get( 'event' )
data = payload.get( 'data' )
print ( f "Received event: { event_type } " )
return jsonify({ 'received' : True }), 200
<? php
function verifyWebhookSignature ( $payload , $signature , $secret ) {
$expectedSignature = hash_hmac ( 'sha256' , json_encode ( $payload ), $secret );
return hash_equals ( $signature , $expectedSignature );
}
// Handle webhook
$payload = json_decode ( file_get_contents ( 'php://input' ), true );
$signature = $_SERVER [ 'HTTP_X_MIHU_SIGNATURE' ] ?? '' ;
$secret = getenv ( 'MIHU_WEBHOOK_SECRET' );
if ( ! verifyWebhookSignature ( $payload , $signature , $secret )) {
http_response_code ( 401 );
echo json_encode ([ 'error' => 'Invalid signature' ]);
exit ;
}
// Process webhook
$eventType = $payload [ 'event' ];
$data = $payload [ 'data' ];
http_response_code ( 200 );
echo json_encode ([ 'received' => true ]);
?>
Best Practices
Never process webhooks without verifying the signature when a secret key is configured. This prevents unauthorized access and spoofed requests.
Only use HTTPS endpoints for webhooks. HTTP is not supported for security reasons.
Return a 200 status code within 5 seconds. Process long-running tasks asynchronously in a background job.
Webhooks may be delivered more than once. Use the event ID to track processed events and prevent duplicate processing.
If your endpoint is temporarily unavailable, Mindhunters will retry delivery. Implement proper error handling to accept retries gracefully.
Keep logs of received webhooks for debugging and audit purposes.
Webhook Endpoint Requirements
Your webhook endpoint must:
✅ Accept POST requests with JSON payloads
✅ Use HTTPS (HTTP not supported)
✅ Return HTTP 200 status code to acknowledge receipt
✅ Respond within 5 seconds
✅ Be publicly accessible on the internet
Endpoints that consistently timeout or return errors may be automatically disabled to prevent delivery issues.
Testing Webhooks
Viewing Webhook Logs
You can view webhook delivery attempts in your Mindhunters dashboard:
Navigate to Developer section
Click on “Logs” in the right panel
Select “Webhook Logs”
Here you’ll see:
Delivery timestamps
Event types
HTTP status codes
Response times
Payload contents
Error messages (if any)
Testing Locally
For local development, use tools like ngrok to expose your local server:
# Install ngrok
npm install -g ngrok
# Expose your local server
ngrok http 3000
# Use the generated HTTPS URL in webhook configuration
# Example: https://abc123.ngrok.io/webhooks/mihu
Manual Testing
You can trigger test webhook events from the Developer section:
Go to Developer → Webhooks
Click “Send Test Event”
Select the event type
Review the test payload
Click “Send”
Example Webhook Handler
Here’s a complete example webhook handler with signature verification and event processing:
Complete Node.js Handler
Complete Python Handler
const express = require ( 'express' );
const crypto = require ( 'crypto' );
const app = express ();
app . use ( express . json ());
const WEBHOOK_SECRET = process . env . MIHU_WEBHOOK_SECRET ;
// Verify webhook signature
function verifySignature ( payload , signature ) {
const hmac = crypto . createHmac ( 'sha256' , WEBHOOK_SECRET );
const expectedSignature = hmac . update ( JSON . stringify ( payload )). digest ( 'hex' );
return crypto . timingSafeEqual (
Buffer . from ( signature ),
Buffer . from ( expectedSignature )
);
}
// Webhook handler
app . post ( '/webhooks/mihu' , async ( req , res ) => {
try {
const signature = req . headers [ 'x-mihu-signature' ];
// Verify signature
if ( ! verifySignature ( req . body , signature )) {
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
const { event , data , timestamp } = req . body ;
// Process different event types
switch ( event ) {
case 'conversation.end' :
await handleConversationEnd ( data );
break ;
case 'conversation.update' :
await handleConversationUpdate ( data );
break ;
case 'intent.triggered' :
await handleIntentTriggered ( data );
break ;
default :
console . log ( `Unhandled event type: ${ event } ` );
}
// Acknowledge receipt
res . status ( 200 ). json ({ received: true , event });
} catch ( error ) {
console . error ( 'Webhook processing error:' , error );
res . status ( 500 ). json ({ error: 'Processing failed' });
}
});
async function handleConversationEnd ( data ) {
console . log ( 'Conversation ended:' , data . id );
console . log ( 'Duration:' , data . duration , 'seconds' );
console . log ( 'Outcome:' , data . summary . outcome );
// Store in database, send notifications, etc.
}
async function handleConversationUpdate ( data ) {
console . log ( 'Conversation update:' , data . id );
console . log ( 'Current status:' , data . status );
// Update real-time dashboard, etc.
}
async function handleIntentTriggered ( data ) {
console . log ( 'Intent triggered:' , data . intent . name );
console . log ( 'Confidence:' , data . intent . confidence );
// Trigger actions based on intent
}
app . listen ( 3000 , () => {
console . log ( 'Webhook server listening on port 3000' );
});
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os
app = Flask( __name__ )
WEBHOOK_SECRET = os.environ.get( 'MIHU_WEBHOOK_SECRET' )
def verify_signature ( payload , signature ):
expected_signature = hmac.new(
WEBHOOK_SECRET .encode( 'utf-8' ),
json.dumps(payload).encode( 'utf-8' ),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@app.route ( '/webhooks/mihu' , methods = [ 'POST' ])
def handle_webhook ():
try :
signature = request.headers.get( 'X-Mindhunters-Signature' )
payload = request.json
# Verify signature
if not verify_signature(payload, signature):
return jsonify({ 'error' : 'Invalid signature' }), 401
event_type = payload.get( 'event' )
data = payload.get( 'data' )
timestamp = payload.get( 'timestamp' )
# Process different event types
if event_type == 'conversation.end' :
handle_conversation_end(data)
elif event_type == 'conversation.update' :
handle_conversation_update(data)
elif event_type == 'intent.triggered' :
handle_intent_triggered(data)
else :
print ( f "Unhandled event type: { event_type } " )
# Acknowledge receipt
return jsonify({ 'received' : True , 'event' : event_type}), 200
except Exception as e:
print ( f "Webhook processing error: { e } " )
return jsonify({ 'error' : 'Processing failed' }), 500
def handle_conversation_end ( data ):
print ( f "Conversation ended: { data[ 'id' ] } " )
print ( f "Duration: { data[ 'duration' ] } seconds" )
print ( f "Outcome: { data[ 'summary' ][ 'outcome' ] } " )
# Store in database, send notifications, etc.
def handle_conversation_update ( data ):
print ( f "Conversation update: { data[ 'id' ] } " )
print ( f "Current status: { data[ 'status' ] } " )
# Update real-time dashboard, etc.
def handle_intent_triggered ( data ):
print ( f "Intent triggered: { data[ 'intent' ][ 'name' ] } " )
print ( f "Confidence: { data[ 'intent' ][ 'confidence' ] } " )
# Trigger actions based on intent
if __name__ == '__main__' :
app.run( port = 3000 )
Retry Policy
If your webhook endpoint fails or times out, Mindhunters will automatically retry delivery:
Initial retry : After 1 minute
Subsequent retries : Exponential backoff (2min, 4min, 8min, 16min)
Maximum retries : 5 attempts
Timeout : 5 seconds per attempt
After exhausting all retries, the webhook event will be marked as failed and logged in the Webhook Logs.
Troubleshooting
Common Issues
Webhooks not being received
Possible causes:
Endpoint URL is not publicly accessible
Firewall blocking incoming requests
Server is down or not responding
Using HTTP instead of HTTPS
Solution: Test your endpoint with tools like Postman or curl, check logs, ensure HTTPS.
Signature verification failing
Possible causes:
Incorrect secret key
Payload modified before verification
Character encoding issues
Solution: Double-check your secret key, verify you’re using the raw request body.
Possible causes:
Processing taking longer than 5 seconds
Database operations blocking response
Solution: Return 200 immediately, process webhook asynchronously in background.
Possible causes:
Retry mechanism delivering same event
Network issues causing retransmission
Solution: Implement idempotency using event IDs to track processed events.
Next Steps
Monitoring Learn how to monitor webhook deliveries and API usage
API Reference Explore all available API endpoints
Error Handling Understand error codes and troubleshooting
Quickstart Build your first integration