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

# Webhooks

> Receive real-time notifications about events in your Mindhunters workspace

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

<CardGroup cols={2}>
  <Card title="Real-time Updates" icon="bolt">
    Get instant notifications when conversations end, status changes occur, or intents are triggered.
  </Card>

  <Card title="Reduced API Calls" icon="chart-line-down">
    No need to poll the API repeatedly. Webhooks push data to you automatically.
  </Card>

  <Card title="Event-Driven Architecture" icon="gears">
    Build reactive applications that respond immediately to conversation events.
  </Card>

  <Card title="Custom Integration" icon="plug">
    Integrate Mindhunters with your existing systems and workflows seamlessly.
  </Card>
</CardGroup>

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

<Steps>
  <Step title="Access Developer Settings">
    1. Log in to your Mindhunters workspace at `https://your-tenant.mindhunters.ai`
    2. Click on your profile icon in the top right corner
    3. Select **"Developer"** from the dropdown menu
  </Step>

  <Step title="Navigate to Webhooks">
    In the Developer section, find the **"Webhooks"** configuration area.
  </Step>

  <Step title="Enter Your Webhook URL">
    1. In the **"Webhook URL"** field, enter your endpoint URL
    2. This must be a publicly accessible HTTPS URL
    3. Example: `https://api.yourcompany.com/webhooks/mihu`

    <Warning>
      HTTP URLs are not supported for security reasons. Your endpoint must use HTTPS.
    </Warning>
  </Step>

  <Step title="Add Secret Key (Recommended)">
    1. Enter a **"Secret Key"** in the provided field
    2. This key will be used to sign webhook payloads
    3. Store this key securely - you'll need it to verify webhook signatures
    4. Use a strong, random string (minimum 32 characters recommended)

    <Tip>
      Generate a secure secret key using: `openssl rand -hex 32`
    </Tip>
  </Step>

  <Step title="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

    <Note>
      Select only the events you need to reduce unnecessary webhook traffic.
    </Note>
  </Step>

  <Step title="Save Configuration">
    Click **"Save"** or **"Update"** to activate your webhook configuration.
  </Step>
</Steps>

## Available Event Types

### Conversation Update

Sent in real-time as the conversation progresses. Useful for live monitoring and transcription display.

**Event Type:** `conversation.update`

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

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

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

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

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

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

```json theme={null}
{
  "event": "event.type",
  "timestamp": "ISO 8601 timestamp",
  "data": {
    // Event-specific data
  },
  "signature": "webhook-signature-hash"
}
```

<ParamField path="event" type="string" required>
  The type of event that triggered the webhook
</ParamField>

<ParamField path="timestamp" type="string" required>
  ISO 8601 formatted timestamp when the event occurred
</ParamField>

<ParamField path="data" type="object" required>
  Event-specific payload containing relevant information
</ParamField>

<ParamField path="signature" type="string">
  HMAC-SHA256 signature for payload verification (when secret key is configured)
</ParamField>

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

<CodeGroup>
  ```javascript Node.js theme={null}
  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 });
  });
  ```

  ```python Python (Flask) theme={null}
  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 PHP theme={null}
  <?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]);
  ?>
  ```
</CodeGroup>

### Best Practices

<AccordionGroup>
  <Accordion title="Always verify signatures" icon="shield-check">
    Never process webhooks without verifying the signature when a secret key is configured. This prevents unauthorized access and spoofed requests.
  </Accordion>

  <Accordion title="Use HTTPS endpoints" icon="lock">
    Only use HTTPS endpoints for webhooks. HTTP is not supported for security reasons.
  </Accordion>

  <Accordion title="Respond quickly" icon="gauge-high">
    Return a 200 status code within 5 seconds. Process long-running tasks asynchronously in a background job.
  </Accordion>

  <Accordion title="Handle idempotency" icon="repeat">
    Webhooks may be delivered more than once. Use the event ID to track processed events and prevent duplicate processing.
  </Accordion>

  <Accordion title="Implement retry logic" icon="arrows-rotate">
    If your endpoint is temporarily unavailable, Mindhunters will retry delivery. Implement proper error handling to accept retries gracefully.
  </Accordion>

  <Accordion title="Log webhook events" icon="file-lines">
    Keep logs of received webhooks for debugging and audit purposes.
  </Accordion>
</AccordionGroup>

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

<Warning>
  Endpoints that consistently timeout or return errors may be automatically disabled to prevent delivery issues.
</Warning>

## Testing Webhooks

### Viewing Webhook Logs

You can view webhook delivery attempts in your Mindhunters dashboard:

1. Navigate to **Developer** section
2. Click on **"Logs"** in the right panel
3. 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:

```bash theme={null}
# 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:

1. Go to **Developer** → **Webhooks**
2. Click **"Send Test Event"**
3. Select the event type
4. Review the test payload
5. Click **"Send"**

## Example Webhook Handler

Here's a complete example webhook handler with signature verification and event processing:

<CodeGroup>
  ```javascript Complete Node.js Handler theme={null}
  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');
  });
  ```

  ```python Complete Python Handler theme={null}
  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)
  ```
</CodeGroup>

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

<AccordionGroup>
  <Accordion title="Webhooks not being received" icon="circle-xmark">
    **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.
  </Accordion>

  <Accordion title="Signature verification failing" icon="key">
    **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.
  </Accordion>

  <Accordion title="Timeouts occurring" icon="clock">
    **Possible causes:**

    * Processing taking longer than 5 seconds
    * Database operations blocking response

    **Solution:** Return 200 immediately, process webhook asynchronously in background.
  </Accordion>

  <Accordion title="Duplicate events" icon="clone">
    **Possible causes:**

    * Retry mechanism delivering same event
    * Network issues causing retransmission

    **Solution:** Implement idempotency using event IDs to track processed events.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Monitoring" icon="chart-line" href="/monitoring">
    Learn how to monitor webhook deliveries and API usage
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference">
    Explore all available API endpoints
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/errors">
    Understand error codes and troubleshooting
  </Card>

  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Build your first integration
  </Card>
</CardGroup>
