Configuring Webhooks
Webhooks enable your application to receive real-time notifications when events occur in Dasha BlackBox. This guide covers everything from initial setup through advanced configuration, troubleshooting, and best practices for production deployments.
What Are Webhooks?
Webhooks are HTTP callbacks that Dasha BlackBox sends to your server when specific events occur. Instead of polling the API repeatedly to check for updates, your server receives instant notifications about:
- Call Events: Call starts, completions, failures
- Agent Updates: Configuration changes, status updates
- Tool Invocations: When agents call your custom functions
- Transfer Events: Call transfers to human operators
Real-Time Integration: Webhooks deliver events within 1-2 seconds of occurrence, enabling immediate responses to customer interactions.
Configuration Methods
Configure webhooks through the dashboard or programmatically via API.
Best For: Initial setup, testing, and non-technical users
- Visual configuration interface
- Test webhook button
- Live delivery monitoring
- No coding required
Best For: Automated deployments, multiple agents, CI/CD pipelines
- Programmatic configuration
- Batch agent creation
- Infrastructure as code
- Version control friendly
Dashboard Configuration
Set up webhooks visually through the Dasha BlackBox dashboard.
Step 1: Navigate to Agent Settings
- Open Dashboard: Log in to Dasha BlackBox Dashboard
- Select Agent: Navigate to Agents and choose your agent
- Access Webhooks: Click Settings tab, then Webhooks section
Enter Webhook Endpoint:
-
Webhook URL Field: Enter your HTTPS endpoint
- Example:
https://api.yourcompany.com/webhooks/blackbox
- Must be publicly accessible
- HTTPS required (HTTP rejected)
-
Validation: Dashboard validates URL format
- Checks for HTTPS protocol
- Verifies URL structure
- Tests DNS resolution
URL Requirements:
- Must use HTTPS (not HTTP)
- Must be publicly accessible (not localhost)
- Must respond with 2xx status code
- Should complete under 10 seconds
Localhost URLs Not Supported: Use ngrok or similar tunneling service for local development. See Testing Webhooks for development setup.
Add authentication headers to secure your webhook endpoint.
Custom Headers:
- Click Add Header button
- Enter header name and value
- Common patterns:
- Bearer tokens:
Authorization: Bearer your-secret-token
- API keys:
X-API-Key: your-api-key
- Custom auth:
X-Webhook-Secret: your-webhook-secret
Example Configurations:
Bearer Token Authentication:
Header Name: Authorization
Header Value: Bearer sk_live_abc123def456ghi789
API Key Authentication:
Header Name: X-API-Key
Header Value: your-api-key-here
Custom Webhook Secret:
Header Name: X-Webhook-Secret
Header Value: whsec_abc123def456
Step 4: Select Event Types
Choose which events trigger webhook notifications.
Available Webhook Payload Types:
Call Events
Agent Events
Function Events
Transfer Events
- StartWebHookPayload: Pre-call webhook when call is initiated
- CompletedWebHookPayload: Call completed successfully with full transcript
- FailedWebHookPayload: Call encountered error
- CallDeadLineWebHookPayload: Call canceled due to deadline expiration
- agent.updated: Agent configuration changed
- agent.status.changed: Agent enabled/disabled
- tool.invoked: Agent called a tool/function
- tool.completed: Tool execution finished
- tool.failed: Tool execution error
- call.transfer.initiated: Transfer to human started
- call.transfer.completed: Transfer successful
Selection Strategy:
- All Events: Maximum observability, higher volume
- Critical Only: CompletedWebHookPayload, FailedWebHookPayload, ToolWebHookPayload
- Custom Selection: Choose specific payload types for your workflow
Event Filtering: Dasha BlackBox sends all events to your webhook URL. Filter unwanted events in your webhook handler rather than relying on server-side filtering.
Set maximum wait time for webhook responses.
Timeout Configuration:
- Default: 10 seconds (recommended)
- Minimum: 5 seconds
- Maximum: 30 seconds
Choosing Timeout:
- 5 seconds: For fast, async processing (recommended)
- 10 seconds: Standard configuration
- 30 seconds: For synchronous processing (use cautiously)
Best Practice: Keep timeouts under 10 seconds. Use async processing with message queues for heavy operations. Respond immediately, process later.
Step 6: Test Webhook
Verify your configuration before enabling.
- Click Test Button: Sends sample event to your endpoint
- Review Response:
- Success: Green checkmark, response displayed
- Failure: Error message with details
- Check Server Logs: Verify webhook received and processed
Test Payload Example:
{
"eventType": "call.completed",
"eventId": "evt_test_abc123",
"timestamp": "2025-01-15T14:30:00Z",
"callId": "call_test_123",
"agentId": "agent_456",
"status": "completed",
"duration": 180
}
Step 7: Save Configuration
- Click Save: Commits webhook configuration
- Confirmation: Dashboard shows success message
- Activation: Webhook active immediately for new events
Configuration Summary:
- Webhook URL:
https://api.yourcompany.com/webhooks/blackbox
- Authentication: Bearer token configured
- Payload Types: StartWebHookPayload, CompletedWebHookPayload, FailedWebHookPayload
- Timeout: 10 seconds
- Status: Active
API Configuration
Configure webhooks programmatically for automated deployments.
Creating Agent with Webhook
Complete Configuration:
const createAgentWithWebhook = async () => {
const response = await fetch('https://blackbox.dasha.ai/api/v1/agents', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: "Customer Support Agent",
description: "Handles customer support inquiries",
config: {
primaryLanguage: "en-US",
llmConfig: {
vendor: "openai",
model: "gpt-4.1-mini",
prompt: "You are a helpful customer support agent.",
options: {
temperature: 0.7,
maxTokens: 800
}
},
ttsConfig: {
vendor: "elevenlabs",
voiceId: "21m00Tcm4TlvDq8ikWAM",
model: "eleven_turbo_v2_5"
},
sttConfig: {
vendor: "deepgram",
model: "nova-2",
language: "en-US"
},
webhookUrl: "https://api.yourcompany.com/webhooks/blackbox",
webhookHeaders: {
"Authorization": "Bearer sk_live_abc123",
"X-Custom-Header": "your-value"
},
webhookTimeout: 10
}
})
});
const agent = await response.json();
console.log('Agent created with webhook:', agent.id);
console.log('Webhook URL:', agent.config.webhookUrl);
return agent;
};
createAgentWithWebhook();
curl -X POST https://blackbox.dasha.ai/api/v1/agents \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Customer Support Agent",
"description": "Handles customer support inquiries",
"config": {
"primaryLanguage": "en-US",
"llmConfig": {
"vendor": "openai",
"model": "gpt-4.1-mini",
"prompt": "You are a helpful customer support agent.",
"options": {
"temperature": 0.7,
"maxTokens": 800
}
},
"ttsConfig": {
"vendor": "elevenlabs",
"voiceId": "21m00Tcm4TlvDq8ikWAM",
"model": "eleven_turbo_v2_5"
},
"sttConfig": {
"vendor": "deepgram",
"model": "nova-2",
"language": "en-US"
},
"webhookUrl": "https://api.yourcompany.com/webhooks/blackbox",
"webhookHeaders": {
"Authorization": "Bearer sk_live_abc123",
"X-Custom-Header": "your-value"
},
"webhookTimeout": 10
}
}'
import requests
import json
def create_agent_with_webhook():
url = 'https://blackbox.dasha.ai/api/v1/agents'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
payload = {
'name': 'Customer Support Agent',
'description': 'Handles customer support inquiries',
'config': {
'primaryLanguage': 'en-US',
'llmConfig': {
'vendor': 'openai',
'model': 'gpt-4.1-mini',
'prompt': 'You are a helpful customer support agent.',
'options': {
'temperature': 0.7,
'maxTokens': 800
}
},
'ttsConfig': {
'vendor': 'elevenlabs',
'voiceId': '21m00Tcm4TlvDq8ikWAM',
'model': 'eleven_turbo_v2_5'
},
'sttConfig': {
'vendor': 'deepgram',
'model': 'nova-2',
'language': 'en-US'
},
'webhookUrl': 'https://api.yourcompany.com/webhooks/blackbox',
'webhookHeaders': {
'Authorization': 'Bearer sk_live_abc123',
'X-Custom-Header': 'your-value'
},
'webhookTimeout': 10
}
}
response = requests.post(url, headers=headers, json=payload)
agent = response.json()
print(f"Agent created with webhook: {agent['id']}")
print(f"Webhook URL: {agent['config']['webhookUrl']}")
return agent
create_agent_with_webhook()
Webhook Configuration Fields
webhookUrl (string, optional)
- Full HTTPS URL to your webhook endpoint
- Must be publicly accessible
- Format:
https://api.example.com/webhooks/blackbox
- Validation: Must use HTTPS, valid URL format
- Example:
https://api.yourcompany.com/webhooks/blackbox
webhookHeaders (object, optional)
- Custom HTTP headers sent with each webhook
- Used for authentication and routing
- Key-value pairs (string: string)
- Common uses: Bearer tokens, API keys, tenant IDs
- Example:
{ "Authorization": "Bearer sk_live_abc123" }
webhookTimeout (integer, optional)
- Maximum seconds to wait for webhook response
- Default: 10 seconds
- Minimum: 5 seconds
- Maximum: 30 seconds
- Recommended: 10 seconds or below
Optional Configuration: All webhook fields are optional. Omit webhookUrl to create an agent without webhooks. Add it later via PUT request.
Webhook URL Requirements
Your webhook endpoint must meet specific requirements for reliable delivery.
HTTPS Only
Requirement: All webhook URLs must use HTTPS.
Why HTTPS Required:
- Protects webhook payloads in transit
- Prevents man-in-the-middle attacks
- Industry standard for webhook security
- Ensures payload integrity
Valid Examples:
✓ https://api.yourcompany.com/webhooks/blackbox
✓ https://webhooks.yourapp.com/blackbox/events
✓ https://your-domain.com:8443/webhooks
Invalid Examples:
✗ http://api.yourcompany.com/webhooks (HTTP not allowed)
✗ localhost:3000/webhook (Localhost not accessible)
✗ 192.168.1.100/webhook (Private IP not accessible)
Public Accessibility
Requirement: Webhook URL must be publicly accessible from internet.
Accessibility Checklist:
Testing Public Accessibility:
# Test from external server or service
curl -I https://api.yourcompany.com/webhooks/blackbox
# Expected response (not 404)
HTTP/2 200
Content-Type: application/json
Development Environments: For local development, use ngrok, localtunnel, or similar to create public URLs. See Testing Webhooks for setup instructions.
Response Requirements
HTTP Status Codes:
Your webhook endpoint must respond with appropriate status codes:
Success (2xx):
200 OK: Webhook received and processed successfully
201 Created: Webhook received, resource created
202 Accepted: Webhook received, will process asynchronously
Client Errors (4xx) - No Retry:
400 Bad Request: Invalid payload format
401 Unauthorized: Authentication failed
404 Not Found: Endpoint not found
Server Errors (5xx) - Triggers Retry:
500 Internal Server Error: Processing failed, retry
502 Bad Gateway: Upstream error, retry
503 Service Unavailable: Temporary issue, retry
Retry Behavior:
Dasha BlackBox automatically retries webhooks that fail with 5xx errors or timeouts:
| Attempt | Delay | When |
|---|
| 1st | Immediate | Initial delivery |
| 2nd | 1 minute | After first failure |
| 3rd | 5 minutes | After second failure |
Idempotency Required: Your webhook handler must be idempotent since retries may cause duplicate delivery. Use event IDs to detect and skip duplicates.
SSL Certificate Validation
Certificate Requirements:
- Issued by trusted Certificate Authority (CA)
- Not expired
- Domain matches webhook URL
- Complete certificate chain provided
Common SSL Issues:
Self-Signed Certificates - Not Accepted:
✗ Self-signed certificate
✗ Expired certificate
✗ Domain mismatch
✗ Incomplete certificate chain
Solutions:
- Use Let’s Encrypt for free trusted certificates
- Purchase certificate from trusted CA
- Use cloud provider managed certificates (AWS ACM, Cloudflare)
Testing SSL Certificate:
# Check certificate validity
openssl s_client -connect api.yourcompany.com:443 -servername api.yourcompany.com
# Or use online tools
# https://www.ssllabs.com/ssltest/
Authentication Setup
Secure your webhook endpoint with authentication headers.
Bearer Token Authentication
Most Common Pattern: Use Bearer tokens in Authorization header.
Configuration:
- Navigate to webhook configuration
- Click Add Header
- Header Name:
Authorization
- Header Value:
Bearer sk_live_your_secret_token
- Click Save
const agent = await fetch('https://blackbox.dasha.ai/api/v1/agents', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: "Agent with Bearer Auth",
config: {
webhookUrl: "https://api.yourcompany.com/webhooks/blackbox",
webhookHeaders: {
"Authorization": "Bearer sk_live_abc123def456ghi789"
}
}
})
});
Server-Side Verification:
// Express.js example
app.post('/webhooks/blackbox', (req, res) => {
const authHeader = req.headers['authorization'];
const expectedToken = 'Bearer ' + process.env.WEBHOOK_TOKEN;
if (authHeader !== expectedToken) {
console.error('Invalid authorization token');
return res.status(401).json({ error: 'Unauthorized' });
}
// Process webhook
console.log('Authenticated webhook:', req.body);
res.status(200).json({ received: true });
});
API Key Authentication
Alternative Pattern: Custom API key header.
Configuration:
// API configuration
{
webhookUrl: "https://api.yourcompany.com/webhooks/blackbox",
webhookHeaders: {
"X-API-Key": "apk_live_abc123def456"
}
}
Server-Side Verification:
app.post('/webhooks/blackbox', (req, res) => {
const apiKey = req.headers['x-api-key'];
const expectedKey = process.env.WEBHOOK_API_KEY;
if (apiKey !== expectedKey) {
return res.status(401).json({ error: 'Invalid API key' });
}
// Process webhook
res.status(200).json({ received: true });
});
HMAC Signature Verification
Most Secure: Verify payload hasn’t been tampered with.
How It Works:
- Dasha BlackBox computes HMAC-SHA256 of payload using shared secret
- Signature sent in
X-Dasha BlackBox-Signature header
- Your server recomputes signature and compares
- Reject if signatures don’t match
Configuration:
// Store webhook secret securely
const webhookSecret = process.env.BLACKBOX_WEBHOOK_SECRET;
// Dasha BlackBox automatically includes signature header
// No configuration needed
Server-Side Verification:
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const signature = req.headers['x-blackbox-signature'];
if (!signature) {
throw new Error('Missing signature');
}
// Extract hash from "sha256=..." format
const receivedHash = signature.replace('sha256=', '');
// Compute expected signature
const payload = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const expectedHash = hmac.digest('hex');
// Timing-safe comparison
const isValid = crypto.timingSafeEqual(
Buffer.from(receivedHash, 'hex'),
Buffer.from(expectedHash, 'hex')
);
if (!isValid) {
throw new Error('Invalid signature');
}
return true;
}
// Usage
app.post('/webhooks/blackbox', (req, res) => {
try {
verifyWebhookSignature(req, process.env.WEBHOOK_SECRET);
// Process webhook
console.log('Valid webhook:', req.body);
res.status(200).json({ received: true });
} catch (error) {
console.error('Signature verification failed:', error);
res.status(401).json({ error: 'Unauthorized' });
}
});
import hmac
import hashlib
import json
def verify_webhook_signature(request, secret):
"""Verify HMAC signature of webhook"""
signature = request.headers.get('X-Dasha BlackBox-Signature')
if not signature:
raise ValueError('Missing signature')
# Extract hash
received_hash = signature.replace('sha256=', '')
# Compute expected signature
payload = json.dumps(request.get_json())
expected_hash = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
# Timing-safe comparison
if not hmac.compare_digest(received_hash, expected_hash):
raise ValueError('Invalid signature')
return True
# Usage in Flask
@app.route('/webhooks/blackbox', methods=['POST'])
def webhook():
try:
secret = os.environ.get('WEBHOOK_SECRET')
verify_webhook_signature(request, secret)
# Process webhook
payload = request.get_json()
print(f"Valid webhook: {payload}")
return jsonify({'received': True}), 200
except ValueError as e:
print(f"Signature verification failed: {e}")
return jsonify({'error': 'Unauthorized'}), 401
Security Best Practice: Use HMAC signature verification in addition to Bearer tokens for maximum security. This prevents replay attacks and payload tampering.
Add custom headers for routing, authentication, or metadata.
Common Use Cases
Multi-Tenant Routing:
{
webhookUrl: "https://api.yourcompany.com/webhooks/blackbox",
webhookHeaders: {
"X-Tenant-ID": "tenant_12345",
"X-Organization-ID": "org_abc"
}
}
Environment Routing:
{
webhookUrl: "https://webhooks.yourcompany.com/blackbox",
webhookHeaders: {
"X-Environment": "production",
"X-Region": "us-east-1"
}
}
Correlation IDs:
{
webhookUrl: "https://api.yourcompany.com/webhooks",
webhookHeaders: {
"X-Correlation-ID": "agent_550e8400",
"X-Service-Name": "blackbox-webhooks"
}
}
Express.js Example:
app.post('/webhooks/blackbox', (req, res) => {
// Access custom headers
const tenantId = req.headers['x-tenant-id'];
const orgId = req.headers['x-organization-id'];
const environment = req.headers['x-environment'];
console.log('Webhook for tenant:', tenantId);
console.log('Organization:', orgId);
console.log('Environment:', environment);
// Route to appropriate handler
const handler = getHandlerForTenant(tenantId);
handler.processWebhook(req.body);
res.status(200).json({ received: true });
});
Flask Example:
@app.route('/webhooks/blackbox', methods=['POST'])
def webhook():
# Access custom headers
tenant_id = request.headers.get('X-Tenant-ID')
org_id = request.headers.get('X-Organization-ID')
environment = request.headers.get('X-Environment')
print(f"Webhook for tenant: {tenant_id}")
print(f"Organization: {org_id}")
print(f"Environment: {environment}")
# Route to appropriate handler
handler = get_handler_for_tenant(tenant_id)
handler.process_webhook(request.get_json())
return jsonify({'received': True}), 200
Character Limits:
- Header name: Maximum 100 characters
- Header value: Maximum 1000 characters
- Total headers: Maximum 10 custom headers
Allowed Characters:
- Header names: Alphanumeric, hyphens, underscores
- Header values: Any UTF-8 characters
Reserved Headers (Cannot Override):
Content-Type: Always application/json
User-Agent: Always Dasha BlackBox-Webhook/1.0
Content-Length: Auto-calculated
X-Dasha BlackBox-Signature: Auto-generated
Sensitive Data: Avoid including sensitive information in header values unless using HTTPS (required). Store secrets in environment variables, not configuration.
Timeout Settings
Configure how long Dasha BlackBox waits for your webhook to respond.
Default Timeout
Default: 10 seconds for all webhook events
Applies To:
- Call lifecycle events (started, ended, completed, failed)
- Agent update events
- Tool invocation events
- Transfer events
Configuring Timeout
- Navigate to agent webhook settings
- Locate Timeout field
- Enter timeout in seconds (5-30)
- Click Save
- Applies to all future webhooks
const agent = await fetch('https://blackbox.dasha.ai/api/v1/agents', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: "Agent with Custom Timeout",
config: {
webhookUrl: "https://api.yourcompany.com/webhooks",
webhookTimeout: 15 // 15 seconds
}
})
});
Choosing the Right Timeout
5 seconds - Recommended:
- Quick acknowledgment pattern
- Async processing with queues
- Fastest retry on failure
- Best for high-volume systems
10 seconds - Default:
- Balanced approach
- Light synchronous processing
- Standard for most use cases
30 seconds - Maximum:
- Heavy synchronous processing
- Complex validation logic
- Use only when necessary
- Risk of call delays
Response Time Best Practices:
// GOOD: Quick response, async processing
app.post('/webhook', (req, res) => {
// Respond immediately
res.status(200).json({ received: true });
// Process asynchronously
setImmediate(() => {
processWebhook(req.body);
});
});
// BAD: Slow synchronous processing
app.post('/webhook', async (req, res) => {
// Don't do this - might timeout
await updateDatabase(req.body);
await sendEmail(req.body);
await callExternalAPI(req.body);
res.status(200).json({ received: true });
});
Production Pattern: Use 5-10 second timeout with async processing. Add webhook to message queue, respond immediately, process later. This ensures fast responses and prevents timeouts.
Retry Configuration
Dasha BlackBox automatically retries failed webhook deliveries with exponential backoff.
Retry Behavior
Automatic Retries: Enabled for all webhooks by default.
Retry Triggers:
- HTTP 5xx status codes (500, 502, 503, 504)
- Connection timeouts (over configured timeout)
- Network errors (DNS failure, connection refused)
- SSL/TLS errors
No Retry:
- HTTP 2xx status codes (success)
- HTTP 4xx status codes (client errors)
- Valid response received
Retry Schedule
Exponential Backoff Strategy:
| Attempt | Delay After Failure | Total Elapsed Time |
|---|
| 1st | Immediate | 0 seconds |
| 2nd | 1 minute | 1 minute |
| 3rd | 5 minutes | 6 minutes |
After 3 Attempts:
- Webhook marked as failed
- No further retry attempts
- Logged in delivery history
- Alert sent to account administrators
Maximum Retries: Dasha BlackBox retries up to 3 times total. Ensure your webhook handler is idempotent to handle potential duplicate deliveries.
Implementing Idempotency
Use Event IDs:
Every webhook includes a unique eventId. Use it to detect duplicates:
// In-memory deduplication (simple)
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
const eventId = req.body.eventId;
// Check if already processed
if (processedEvents.has(eventId)) {
console.log('Duplicate event, skipping:', eventId);
return res.status(200).json({
received: true,
duplicate: true
});
}
// Process webhook
processedEvents.add(eventId);
processWebhook(req.body);
res.status(200).json({ received: true });
});
Database-Backed Deduplication:
// Production-ready with persistence
app.post('/webhook', async (req, res) => {
const eventId = req.body.eventId;
// Atomic check-and-insert
const inserted = await db.query(`
INSERT INTO processed_webhooks (event_id, received_at)
VALUES ($1, NOW())
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id
`, [eventId]);
if (inserted.rows.length === 0) {
console.log('Duplicate event:', eventId);
return res.status(200).json({
received: true,
duplicate: true
});
}
// Process webhook (guaranteed first time)
await processWebhook(req.body);
res.status(200).json({ received: true });
});
Retry Optimization
Fast Failure for 4xx Errors:
app.post('/webhook', (req, res) => {
// Validate payload quickly
if (!req.body.eventType || !req.body.eventId) {
// Return 400 to prevent retries
return res.status(400).json({
error: 'Invalid payload'
});
}
// Authentication failure
if (!isValidAuth(req.headers)) {
// Return 401 to prevent retries
return res.status(401).json({
error: 'Unauthorized'
});
}
// Process webhook
processWebhook(req.body);
res.status(200).json({ received: true });
});
Temporary Failures with 5xx:
app.post('/webhook', async (req, res) => {
try {
await processWebhook(req.body);
res.status(200).json({ received: true });
} catch (error) {
console.error('Processing failed:', error);
// Return 500 to trigger retry
res.status(500).json({
error: 'Temporary processing error',
message: error.message
});
}
});
Multiple Webhooks
Dasha BlackBox supports one webhook URL per agent. For multiple destinations, use a webhook proxy.
Single Webhook Limitation
Current Behavior:
- One
webhookUrl per agent
- Cannot configure multiple endpoints directly
- All events go to single URL
Workaround: Implement webhook proxy/router.
Webhook Proxy Pattern
Architecture:
Dasha BlackBox → Your Proxy Server → Multiple Endpoints
└→ CRM System
└→ Analytics Platform
└→ Notification Service
Implementation:
Node.js Proxy
Event-Based Router
Queue-Based
const express = require('express');
const app = express();
app.use(express.json());
// Webhook destinations
const destinations = [
'https://crm.yourcompany.com/webhooks/blackbox',
'https://analytics.yourcompany.com/webhooks',
'https://notifications.yourcompany.com/hooks'
];
app.post('/webhook-proxy', async (req, res) => {
const webhook = req.body;
// Respond immediately to Dasha BlackBox
res.status(200).json({ received: true });
// Fan out to all destinations asynchronously
const promises = destinations.map(url =>
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(webhook)
}).catch(err => {
console.error(`Failed to forward to ${url}:`, err);
})
);
await Promise.allSettled(promises);
});
app.listen(3000);
// Route different payload types to different endpoints
const payloadRoutes = {
'StartWebHookPayload': [
'https://crm.yourcompany.com/webhooks'
],
'CompletedWebHookPayload': [
'https://crm.yourcompany.com/webhooks',
'https://analytics.yourcompany.com/webhooks'
],
'FailedWebHookPayload': [
'https://alerts.yourcompany.com/webhooks'
],
'ToolWebHookPayload': [
'https://functions.yourcompany.com/webhooks'
]
};
app.post('/webhook-router', async (req, res) => {
const webhook = req.body;
const payloadType = webhook.type;
// Respond to Dasha BlackBox immediately
res.status(200).json({ received: true });
// Route to appropriate destinations
const destinations = payloadRoutes[payloadType] || [];
const promises = destinations.map(url =>
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(webhook)
}).catch(err => {
console.error(`Failed to route ${payloadType} to ${url}:`, err);
})
);
await Promise.allSettled(promises);
});
// Use message queue for reliable multi-destination delivery
const { Queue } = require('bull');
const webhookQueue = new Queue('webhooks');
app.post('/webhook-queue', async (req, res) => {
// Add to queue
await webhookQueue.add({
webhook: req.body,
receivedAt: new Date().toISOString()
});
// Respond immediately
res.status(200).json({ received: true });
});
// Process queue with multiple workers
webhookQueue.process(async (job) => {
const { webhook } = job.data;
// Fan out to all destinations
await Promise.allSettled([
sendToCRM(webhook),
sendToAnalytics(webhook),
sendToNotifications(webhook)
]);
});
Conditional Routing
Route by Event Type:
app.post('/webhook-conditional', async (req, res) => {
const payload = req.body;
const { type } = payload;
// Respond to Dasha BlackBox
res.status(200).json({ received: true });
// Conditional routing based on payload type
if (type === 'CompletedWebHookPayload') {
await sendToCRM(payload);
await sendToAnalytics(payload);
}
if (type === 'FailedWebHookPayload') {
await sendToAlerts(payload);
}
if (type === 'ToolWebHookPayload') {
await sendToFunctionLogger(payload);
}
// Route based on call data
if (payload.durationSeconds > 300) { // over 5 minutes
await sendToQualityAssurance(payload);
}
});
Testing Configuration
Verify your webhook configuration before production deployment.
Testing with webhook.site
Quick Validation:
- Get Test URL: Visit https://webhook.site
- Configure in Dasha BlackBox: Use provided URL as webhook endpoint
- Trigger Events: Make test calls or use webhook test API
- Inspect Requests: View full HTTP request details
Testing with Dasha BlackBox Test API
Send Test Webhook:
const testWebhook = async () => {
const response = await fetch('https://blackbox.dasha.ai/api/v1/webhooks/test', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
webHook: {
url: 'https://your-server.com/webhooks/blackbox',
headers: {
'Authorization': 'Bearer your-webhook-secret'
}
},
webhookType: 'CompletedWebHookPayload',
agentAdditionalData: {
companyName: 'Test Company'
},
callAdditionalData: {
userId: 'test-user-123'
}
})
});
const result = await response.json();
console.log('Test successful:', result.success);
console.log('HTTP status:', result.httpStatus);
console.log('Response:', result.responseData);
console.log('Time taken:', result.timeTaken, 'ms');
if (!result.success) {
console.error('Error:', result.error);
}
};
testWebhook();
Testing with ngrok
Local Development Testing:
# Terminal 1: Start your webhook server
node webhook-server.js
# Terminal 2: Start ngrok tunnel
ngrok http 3000
# Output: https://abc123.ngrok.io -> http://localhost:3000
# Terminal 3: Test with Dasha BlackBox
curl -X POST https://blackbox.dasha.ai/api/v1/webhooks/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webHook": {
"url": "https://abc123.ngrok.io/webhook"
},
"webhookType": "CompletedWebHookPayload"
}'
See Testing Webhooks for complete ngrok setup guide.
Updating Webhooks
Modify webhook configuration for existing agents.
Updating via Dashboard
- Navigate to Agent: Open agent settings
- Edit Webhooks: Click Webhooks section
- Modify Configuration: Update URL, headers, or timeout
- Save Changes: Click Save button
- Verify: Test updated configuration
Updating via API
Update Webhook URL:
const updateWebhook = async (agentId) => {
const response = await fetch(`https://blackbox.dasha.ai/api/v1/agents/${agentId}`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
config: {
webhookUrl: "https://api.yourcompany.com/webhooks/blackbox-v2",
webhookHeaders: {
"Authorization": "Bearer new_token_abc123"
},
webhookTimeout: 10
}
})
});
const agent = await response.json();
console.log('Webhook updated:', agent.config.webhookUrl);
};
updateWebhook('550e8400-e29b-41d4-a716-446655440000');
Update Headers Only:
const updateHeaders = async (agentId) => {
const response = await fetch(`https://blackbox.dasha.ai/api/v1/agents/${agentId}`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
config: {
webhookHeaders: {
"Authorization": "Bearer updated_token_xyz789",
"X-Tenant-ID": "tenant_new_456"
}
}
})
});
const agent = await response.json();
console.log('Headers updated');
};
Update Timing
Immediate Effect:
- Changes apply to new events immediately
- Active calls continue with old configuration
- No restart or downtime required
Migration Pattern:
// Safe migration to new webhook endpoint
const migrateWebhook = async (agentId) => {
// Step 1: Deploy new webhook endpoint
// Step 2: Verify new endpoint works (test)
await testWebhook('https://api.yourcompany.com/webhooks/v2');
// Step 3: Update agent configuration
await updateWebhook(agentId, {
webhookUrl: 'https://api.yourcompany.com/webhooks/v2'
});
// Step 4: Monitor new endpoint
// Step 5: Decommission old endpoint after 24h
};
Disabling Webhooks
Temporarily or permanently disable webhook notifications.
Temporary Disable
Dashboard Method:
- Navigate to agent webhook settings
- Toggle Enabled switch to OFF
- Click Save
- Webhooks paused, configuration preserved
API Method:
// Disable by setting URL to null
const disableWebhook = async (agentId) => {
const response = await fetch(`https://blackbox.dasha.ai/api/v1/agents/${agentId}`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
config: {
webhookUrl: null
}
})
});
const agent = await response.json();
console.log('Webhook disabled');
};
disableWebhook('550e8400-e29b-41d4-a716-446655440000');
Permanent Removal
Remove Configuration:
const removeWebhook = async (agentId) => {
const response = await fetch(`https://blackbox.dasha.ai/api/v1/agents/${agentId}`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
config: {
webhookUrl: null,
webhookHeaders: null,
webhookTimeout: null
}
})
});
const agent = await response.json();
console.log('Webhook configuration removed');
};
Re-Enabling Webhooks
Restore Configuration:
const enableWebhook = async (agentId) => {
const response = await fetch(`https://blackbox.dasha.ai/api/v1/agents/${agentId}`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
config: {
webhookUrl: "https://api.yourcompany.com/webhooks/blackbox",
webhookHeaders: {
"Authorization": "Bearer sk_live_abc123"
},
webhookTimeout: 10
}
})
});
const agent = await response.json();
console.log('Webhook re-enabled:', agent.config.webhookUrl);
};
Troubleshooting
Common webhook configuration issues and solutions.
Webhook Not Receiving Events
Symptoms:
- No webhooks arriving at endpoint
- Zero delivery attempts in logs
- Events occurring but no notifications
Troubleshooting Steps:
- Verify Configuration:
// Get current agent configuration
const agent = await fetch(`https://blackbox.dasha.ai/api/v1/agents/${agentId}`, {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
}).then(r => r.json());
console.log('Webhook URL:', agent.config.webhookUrl);
console.log('Headers:', agent.config.webhookHeaders);
console.log('Timeout:', agent.config.webhookTimeout);
- Test Endpoint Accessibility:
# Test from external server
curl -X POST https://your-webhook-url.com/webhooks \
-H "Content-Type: application/json" \
-d '{"test": true}'
# Should return 200 OK
- Check Firewall Rules:
- Verify port 443 (HTTPS) is open
- Whitelist Dasha BlackBox IP ranges (if applicable)
- Check security group rules
- Test with webhook.site:
- Replace URL with webhook.site URL
- Trigger events
- Verify webhooks arrive at webhook.site
Webhooks Timing Out
Symptoms:
- Delivery marked as failed with timeout error
- Retries exhausted
- Server logs show request started but no response
Solutions:
Optimize Response Time:
// BAD: Slow synchronous processing
app.post('/webhook', async (req, res) => {
await heavyProcessing(req.body); // Takes 15 seconds
res.status(200).json({ received: true });
});
// GOOD: Quick response, async processing
app.post('/webhook', (req, res) => {
res.status(200).json({ received: true }); // Immediate
setImmediate(() => {
heavyProcessing(req.body); // Happens after response
});
});
Use Message Queue:
const queue = require('bull');
const webhookQueue = new queue('webhooks');
app.post('/webhook', async (req, res) => {
await webhookQueue.add(req.body); // Fast queue add
res.status(200).json({ received: true });
});
webhookQueue.process(async (job) => {
await heavyProcessing(job.data); // No time limit
});
Reduce Timeout Setting:
// Force quick responses
{
webhookUrl: "https://api.yourcompany.com/webhooks",
webhookTimeout: 5 // 5 seconds maximum
}
Authentication Failures
Symptoms:
- 401 Unauthorized responses
- Signature verification failures
- Header missing errors
Troubleshooting:
Verify Headers Sent:
app.post('/webhook', (req, res) => {
console.log('All headers:', req.headers);
console.log('Authorization:', req.headers['authorization']);
console.log('X-API-Key:', req.headers['x-api-key']);
res.status(200).json({ received: true });
});
Check Header Case:
// Headers are lowercase in Node.js
const authHeader = req.headers['authorization']; // Correct
const authHeader = req.headers['Authorization']; // Wrong (undefined)
Verify Signature Computation:
// Debug signature verification
const receivedSig = req.headers['x-blackbox-signature'];
const payload = JSON.stringify(req.body);
const expectedSig = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
console.log('Received:', receivedSig);
console.log('Expected:', expectedSig);
console.log('Match:', receivedSig === expectedSig);
SSL Certificate Errors
Symptoms:
- SSL handshake failures
- Certificate verification errors
- “Invalid certificate” in logs
Solutions:
Check Certificate Validity:
# Test SSL certificate
openssl s_client -connect your-domain.com:443 -servername your-domain.com
# Check expiration
echo | openssl s_client -connect your-domain.com:443 2>/dev/null | \
openssl x509 -noout -dates
Use Trusted Certificate:
- Obtain certificate from trusted CA (Let’s Encrypt, DigiCert)
- Avoid self-signed certificates
- Ensure complete certificate chain
Verify Domain Match:
# Certificate should match webhook domain
echo | openssl s_client -connect your-domain.com:443 2>/dev/null | \
openssl x509 -noout -text | grep DNS
High Retry Rate
Symptoms:
- Many retry attempts in logs
- Same webhooks delivered multiple times
- 500 errors in server logs
Solutions:
Improve Error Handling:
app.post('/webhook', async (req, res) => {
try {
await processWebhook(req.body);
res.status(200).json({ received: true });
} catch (error) {
console.error('Processing error:', error);
// Distinguish temporary vs permanent errors
if (error.code === 'ECONNREFUSED') {
// Temporary - trigger retry
res.status(503).json({ error: 'Service unavailable' });
} else {
// Permanent - don't retry
res.status(400).json({ error: 'Invalid payload' });
}
}
});
Add Circuit Breaker:
const circuitBreaker = require('opossum');
const processWebhookSafe = circuitBreaker(processWebhook, {
timeout: 5000, // 5 second timeout
errorThresholdPercentage: 50, // Open after 50% errors
resetTimeout: 30000 // Try again after 30 seconds
});
app.post('/webhook', async (req, res) => {
try {
await processWebhookSafe(req.body);
res.status(200).json({ received: true });
} catch (error) {
res.status(503).json({ error: 'Service temporarily unavailable' });
}
});
Validation Checklist
Before going to production, verify your webhook configuration:
Endpoint Requirements:
Security:
Reliability:
Testing:
Operations:
Next Steps
Now that you’ve configured webhooks:
API Cross-References