Testing Webhooks
Testing webhooks before deploying to production is essential for ensuring your integration works correctly. This guide covers multiple testing approaches, from using the Dasha BlackBox dashboard to setting up local development environments.
Why Test Webhooks?
Testing webhooks helps you:
- Validate Endpoints: Ensure your webhook URL is accessible and responds correctly
- Verify Payloads: Confirm your server handles all event types properly
- Debug Integration: Identify configuration issues before going live
- Test Security: Validate signature verification and authentication
- Check Error Handling: Ensure your server handles retries and timeouts correctly
Best Practice: Always test webhooks in a staging environment before enabling them in production. This prevents disrupting live operations during debugging.
Testing Methods Overview
Choose the testing approach that best fits your workflow:
Dashboard Testing
webhook.site
Local Testing
Production Testing
Best For: Quick validation during development
- Test from Dasha BlackBox dashboard
- No external tools required
- Instant feedback on response
- Simulates all webhook event types
Best For: Inspecting webhook payloads
- Free temporary URLs
- View full HTTP requests
- No server setup needed
- Great for learning payload structure
Best For: Full integration testing
- Test with your actual application
- Debug webhook handlers
- Validate business logic
- Use ngrok or localtunnel
Best For: Final validation
- Test live webhook configuration
- Verify security settings
- Validate retry behavior
- Monitor delivery logs
Method 1: Dashboard Testing
The Dasha BlackBox dashboard provides a built-in webhook testing tool that sends realistic payloads to your endpoint.
- Navigate to the Webhooks section in your dashboard
- Click Test Webhook button
- Configure test parameters
- Send test request
- Review response
Using the API Test Endpoint
You can also test webhooks programmatically using the API:
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/webhook',
headers: {
'X-Custom-Header': 'your-value'
}
},
webhookType: 'CompletedWebHookPayload',
agentAdditionalData: {
companyName: 'Test Company'
},
callAdditionalData: {
userId: 'test-user-123'
}
})
});
const result = await response.json();
console.log('Success:', result.success);
console.log('HTTP Status:', result.httpStatus);
console.log('Response Data:', result.responseData);
console.log('Time Taken:', result.timeTaken, 'ms');
if (!result.success) {
console.error('Error:', result.error);
}
};
testWebhook();
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://your-server.com/webhook",
"headers": {
"X-Custom-Header": "your-value"
}
},
"webhookType": "CompletedWebHookPayload",
"agentAdditionalData": {
"companyName": "Test Company"
},
"callAdditionalData": {
"userId": "test-user-123"
}
}'
import requests
import json
def test_webhook():
url = 'https://blackbox.dasha.ai/api/v1/webhooks/test'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
payload = {
'webHook': {
'url': 'https://your-server.com/webhook',
'headers': {
'X-Custom-Header': 'your-value'
}
},
'webhookType': 'CompletedWebHookPayload',
'agentAdditionalData': {
'companyName': 'Test Company'
},
'callAdditionalData': {
'userId': 'test-user-123'
}
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
print(f"Success: {result['success']}")
print(f"HTTP Status: {result['httpStatus']}")
print(f"Response Data: {result['responseData']}")
print(f"Time Taken: {result['timeTaken']} ms")
if not result['success']:
print(f"Error: {result['error']}")
test_webhook()
Understanding Test Results
The test endpoint returns detailed information about the webhook delivery:
Success Response Example:
{
"success": true,
"httpStatus": 200,
"httpStatusText": "OK",
"responseData": {
"message": "Webhook received successfully",
"processedAt": "2025-01-15T14:30:00Z"
},
"validationResult": null,
"error": null,
"timeTaken": 245
}
Failed Response Example:
{
"success": false,
"httpStatus": 500,
"httpStatusText": "Internal Server Error",
"responseData": null,
"validationResult": null,
"error": "Connection timeout after 30 seconds",
"timeTaken": 30000
}
Response Fields:
success: Whether webhook delivery succeeded
httpStatus: HTTP status code from your endpoint
httpStatusText: Status text description
responseData: Parsed response body from your endpoint
validationResult: Validation errors for StartWebHookPayload responses
error: Error message if delivery failed
timeTaken: Request duration in milliseconds
Timeout Limit: Webhook endpoints must respond within 30 seconds. If your endpoint takes longer, the test will fail with a timeout error.
Method 2: Testing with webhook.site
webhook.site provides free temporary URLs for inspecting webhook requests without setting up a server.
Setup Process
-
Get Your Temporary URL:
- Visit https://webhook.site
- You’ll receive a unique URL immediately
- Example:
https://webhook.site/abc123-def456-ghi789
-
Configure in Dasha BlackBox Dashboard:
- Copy the webhook.site URL
- Add it to your agent’s webhook configuration
- Configure which events to receive
-
Trigger Test Webhooks:
- Use the Dasha BlackBox test tool (Method 1)
- Or trigger actual events (make test calls)
-
Inspect Requests:
- View full HTTP request details
- See headers, body, and query parameters
- Copy payloads for testing
Analyzing Webhook Payloads
webhook.site displays all details of incoming requests:
HTTP Headers:
POST /abc123-def456-ghi789 HTTP/1.1
Host: webhook.site
Content-Type: application/json
X-Dasha BlackBox-Signature: sha256=abc123...
X-Dasha BlackBox-Event: call.completed
User-Agent: Dasha BlackBox-Webhook/1.0
Request Body:
{
"eventType": "call.completed",
"timestamp": "2025-01-15T14:30:00Z",
"callId": "call_abc123",
"agentId": "agent_xyz789",
"status": "completed",
"duration": 180,
"transcript": [
{
"speaker": "agent",
"text": "Hello! How can I help you today?",
"timestamp": "2025-01-15T14:28:00Z"
},
{
"speaker": "user",
"text": "I'd like to book an appointment.",
"timestamp": "2025-01-15T14:28:05Z"
}
]
}
Using webhook.site for Development
Workflow:
- Copy Payload Structure: Use webhook.site to see real payload formats
- Build Handler: Create webhook handler matching payload structure
- Switch to ngrok: Replace webhook.site URL with local development URL
- Test Integration: Verify your handler processes webhooks correctly
Payload Library: Save example payloads from webhook.site for each event type. Use these as test fixtures when writing unit tests for your webhook handlers.
Method 3: Testing with RequestBin and Beeceptor
Alternative tools for webhook inspection with additional features.
RequestBin
Free Plan: https://requestbin.com
Features:
- Create temporary public endpoints
- View request history
- Inspect headers and body
- Built-in JSON formatting
- Custom response configuration
Setup:
# 1. Create a bin at requestbin.com
# You'll get: https://requestbin.com/r/abc123
# 2. 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://requestbin.com/r/abc123"
},
"webhookType": "CompletedWebHookPayload"
}'
# 3. View results at requestbin.com
Beeceptor
Free Plan: https://beeceptor.com
Features:
- Mock API endpoints
- Custom response rules
- Request matching patterns
- Latency simulation
- Response customization
Setup:
// 1. Create endpoint at beeceptor.com
// Example: https://my-webhook-test.free.beeceptor.com
// 2. Configure custom response rules
// Beeceptor dashboard -> Rules -> Add Rule:
{
"request": {
"path": "/webhook",
"method": "POST"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"received": true,
"timestamp": "{{now}}"
}
}
}
// 3. Test from Dasha BlackBox
const testResult = 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://my-webhook-test.free.beeceptor.com/webhook'
},
webhookType: 'StartWebHookPayload'
})
});
Advanced Use Cases:
- Simulate Errors: Configure Beeceptor to return 500 errors to test retry logic
- Test Timeouts: Add latency rules to simulate slow endpoints
- Validate Headers: Check that Dasha BlackBox sends correct headers
Method 4: Local Testing with ngrok
ngrok creates secure tunnels to your localhost, enabling testing with your actual application.
ngrok Setup
Installation:
# Using Homebrew
brew install ngrok
# Or download from ngrok.com
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | \
sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
# Using Chocolatey
choco install ngrok
# Or download from ngrok.com and add to PATH
# Download and install
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | \
sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && \
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | \
sudo tee /etc/apt/sources.list.d/ngrok.list && \
sudo apt update && sudo apt install ngrok
Authentication:
# Sign up at ngrok.com and get your auth token
ngrok config add-authtoken YOUR_AUTH_TOKEN
Starting a Tunnel
Basic Tunnel:
# Start local webhook server on port 3000
ngrok http 3000
# Output:
# Forwarding https://abc123.ngrok.io -> http://localhost:3000
Custom Subdomain (paid plan):
ngrok http 3000 --subdomain=my-blackbox-webhooks
# URL: https://my-blackbox-webhooks.ngrok.io
Local Webhook Server Example
Node.js + Express:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Webhook signature verification
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload));
const expectedSignature = 'sha256=' + hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Webhook endpoint
app.post('/webhook', (req, res) => {
console.log('Received webhook:', req.body);
// Verify signature
const signature = req.headers['x-blackbox-signature'];
const isValid = verifySignature(req.body, signature, process.env.WEBHOOK_SECRET);
if (!isValid) {
console.error('Invalid signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook based on payload type
const { type, callId, agentId, status } = req.body;
switch (type) {
case 'StartWebHookPayload':
console.log(`Call ${callId} started for agent ${agentId}`);
break;
case 'CompletedWebHookPayload':
console.log(`Call ${callId} completed with status: ${status}`);
break;
case 'FailedWebHookPayload':
console.error(`Call ${callId} failed: ${req.body.errorMessage}`);
break;
default:
console.log('Unknown payload type:', type);
}
// Respond quickly (under 5 seconds)
res.status(200).json({
received: true,
timestamp: new Date().toISOString()
});
});
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Webhook server listening on port ${PORT}`);
console.log('Start ngrok: ngrok http 3000');
});
Python + Flask:
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os
app = Flask(__name__)
def verify_signature(payload, signature, secret):
"""Verify HMAC signature"""
expected = 'sha256=' + hmac.new(
secret.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.get_json()
print(f"Received webhook: {payload}")
# Verify signature
signature = request.headers.get('X-Dasha BlackBox-Signature')
secret = os.environ.get('WEBHOOK_SECRET')
if not verify_signature(payload, signature, secret):
print("Invalid signature")
return jsonify({'error': 'Invalid signature'}), 401
# Process webhook based on payload type
payload_type = payload.get('type')
call_id = payload.get('callId')
if payload_type == 'StartWebHookPayload':
print(f"Call {call_id} started")
elif payload_type == 'CompletedWebHookPayload':
print(f"Call {call_id} completed")
elif payload_type == 'FailedWebHookPayload':
print(f"Call {call_id} failed: {payload.get('errorMessage')}")
# Respond quickly
return jsonify({
'received': True,
'timestamp': datetime.utcnow().isoformat()
}), 200
@app.route('/health')
def health():
return jsonify({'status': 'healthy'}), 200
if __name__ == '__main__':
app.run(port=3000, debug=True)
Testing with ngrok
Complete Workflow:
# 1. Start your webhook server
node webhook-server.js
# or
python webhook-server.py
# 2. In another terminal, start ngrok
ngrok http 3000
# 3. Copy the HTTPS URL from ngrok output
# Example: https://abc123.ngrok.io
# 4. Test with Dasha BlackBox API
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",
"headers": {
"X-Custom-Auth": "your-secret-token"
}
},
"webhookType": "CompletedWebHookPayload"
}'
# 5. Check your server logs for the received webhook
# 6. Inspect the request in ngrok web interface (http://127.0.0.1:4040)
ngrok Web Interface
ngrok provides a local web UI for inspecting requests:
- Access: Open
http://127.0.0.1:4040 in your browser
- View Requests: See all HTTP requests sent through the tunnel
- Replay Requests: Resend previous requests for debugging
- Inspect Details: View headers, body, response, and timing
Development Workflow: Use ngrok’s replay feature to resend the same webhook multiple times while debugging your handler code. This saves time compared to triggering new webhooks from Dasha BlackBox.
Method 5: Local Testing with localtunnel
localtunnel is a free alternative to ngrok with no account required.
localtunnel Setup
Installation:
npm install -g localtunnel
Start Tunnel:
# Start your webhook server first
node webhook-server.js
# Then create tunnel in another terminal
lt --port 3000
# Output:
# your url is: https://lucky-cats-12345.loca.lt
Custom Subdomain:
lt --port 3000 --subdomain my-blackbox-webhooks
# URL: https://my-blackbox-webhooks.loca.lt
Testing with localtunnel
Complete Example:
// webhook-server.js
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
console.log('Webhook received:', JSON.stringify(req.body, null, 2));
res.status(200).json({ received: true });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
console.log('Start localtunnel: lt --port 3000');
});
# Terminal 1: Start server
node webhook-server.js
# Terminal 2: Start tunnel
lt --port 3000
# Terminal 3: Test webhook
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://lucky-cats-12345.loca.lt/webhook"
},
"webhookType": "StartWebHookPayload",
"toolName": "check_availability",
"toolArguments": {
"date": "2025-01-20",
"time": "14:00"
}
}'
localtunnel vs ngrok
| Feature | localtunnel | ngrok |
|---|
| Cost | Free | Free tier + paid plans |
| Account Required | No | Yes |
| Custom Subdomains | Sometimes available | Paid plan only |
| Stability | Good | Excellent |
| Web UI | No | Yes (very useful) |
| Speed | Good | Better |
| Features | Basic tunneling | Advanced inspection tools |
Recommendation: Use localtunnel for quick tests and simple debugging. Use ngrok for extended development sessions and advanced debugging with the web UI.
Testing Different Event Types
Dasha BlackBox supports multiple webhook event types. Test each one to ensure your handler works correctly.
Available Event Types
Call Events
Agent Events
Tool Events
StartWebHookPayload
- Sent when call starts
- Can modify agent behavior
- Must respond under 5 seconds
CompletedWebHookPayload
- Sent when call completes successfully
- Contains full transcript
- Includes call duration and metadata
FailedWebHookPayload
- Sent when call fails
- Contains error information
- Includes failure reason
CallDeadLineWebHookPayload
- Sent when scheduled call time approaches
- Warning before call execution
- Allows last-minute cancellation
TransferWebHookPayload
- Sent when call transfer requested
- Contains transfer reason
- Includes conversation context
ToolWebHookPayload
- Sent when agent calls a function
- Contains function name and arguments
- Expects structured response
- Response used in conversation
Testing StartWebHookPayload
This event occurs at call start and can modify agent configuration:
// Test start webhook
const testStartWebhook = 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/webhook/start',
headers: {
'Authorization': 'Bearer YOUR_WEBHOOK_SECRET'
}
},
webhookType: 'StartWebHookPayload',
agentAdditionalData: {
companyName: 'Acme Corp',
supportLevel: 'premium'
},
callAdditionalData: {
customerId: 'cust_12345',
accountType: 'enterprise'
}
})
});
const result = await response.json();
console.log('Start webhook test:', result);
// Check if your endpoint returns valid StartWebhookResponse
if (result.validationResult && !result.validationResult.isValid) {
console.error('Validation errors:', result.validationResult.errors);
}
};
Expected Webhook Payload:
{
"type": "StartWebHookPayload",
"status": "Queued",
"callId": "call_test_abc123",
"agentId": "agent_xyz789",
"orgId": "org_123abc",
"endpoint": "+12025551234",
"agentAdditionalData": {
"companyName": "Acme Corp",
"supportLevel": "premium"
},
"callAdditionalData": {
"customerId": "cust_12345",
"accountType": "enterprise"
},
"originalAgentConfig": {
"primaryLanguage": "en-US",
"llmConfig": { "vendor": "openai", "model": "gpt-4.1-mini" }
}
}
Valid Response Format:
{
"systemPrompt": "You are a premium support agent for Acme Corp. The customer is an enterprise client (ID: cust_12345). Provide expedited service.",
"additionalData": {
"customerHistory": {
"lastCallDate": "2025-01-10",
"issuesResolved": 5
}
}
}
Testing CompletedWebHookPayload
This event occurs after call completion:
// Test completed webhook
const testCompletedWebhook = 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/webhook/completed'
},
webhookType: 'CompletedWebHookPayload',
callAdditionalData: {
orderId: 'order_98765',
appointmentBooked: true
}
})
});
const result = await response.json();
console.log('Completed webhook test:', result);
};
Expected Payload:
{
"type": "CompletedWebHookPayload",
"status": "Completed",
"callId": "call_test_abc123",
"agentId": "agent_xyz789",
"orgId": "org_123abc",
"callType": "OutboundAudio",
"createdTime": "2025-01-15T14:30:00.000Z",
"completedTime": "2025-01-15T14:33:00.000Z",
"durationSeconds": 180,
"inspectorUrl": "https://blackbox.dasha.ai/calls/call_test_abc123",
"transcription": [
{
"speaker": "assistant",
"text": "Hello! How can I help you today?",
"startTime": "2025-01-15T14:30:05.000Z",
"endTime": "2025-01-15T14:30:08.000Z"
},
{
"speaker": "user",
"text": "I need to book an appointment.",
"startTime": "2025-01-15T14:30:10.000Z",
"endTime": "2025-01-15T14:30:13.000Z"
}
],
"result": {
"finishReason": "user_hangup",
"status": "success"
},
"callAdditionalData": {
"orderId": "order_98765",
"appointmentBooked": true
},
"agentAdditionalData": {}
}
Testing FailedWebHookPayload
Test error handling and retry logic:
// Test failed webhook
const testFailedWebhook = 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/webhook/failed'
},
webhookType: 'FailedWebHookPayload',
callAdditionalData: {
attemptNumber: 3,
originalCallTime: '2025-01-15T14:00:00Z'
}
})
});
const result = await response.json();
console.log('Failed webhook test:', result);
};
Expected Payload:
{
"type": "FailedWebHookPayload",
"status": "Failed",
"callId": "call_test_abc123",
"agentId": "agent_xyz789",
"orgId": "org_123abc",
"callType": "OutboundAudio",
"createdTime": "2025-01-15T14:30:00.000Z",
"completedTime": "2025-01-15T14:31:00.000Z",
"errorMessage": "Call did not connect within timeout period",
"inspectorUrl": "https://blackbox.dasha.ai/calls/call_test_abc123",
"callAdditionalData": {
"attemptNumber": 3,
"originalCallTime": "2025-01-15T14:00:00Z"
},
"agentAdditionalData": {}
}
Test function calling integration:
// Test tool webhook
const testToolWebhook = 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/webhook/tool'
},
webhookType: 'ToolWebHookPayload',
toolName: 'check_availability',
toolArguments: {
date: '2025-01-20',
time: '14:00',
duration: 30
},
agentAdditionalData: {
businessId: 'biz_12345'
}
})
});
const result = await response.json();
console.log('Tool webhook test:', result);
console.log('Your tool response:', result.responseData);
};
Expected Payload:
{
"eventType": "tool.called",
"timestamp": "2025-01-15T14:30:30Z",
"callId": "call_test_abc123",
"agentId": "agent_xyz789",
"toolName": "check_availability",
"toolArguments": {
"date": "2025-01-20",
"time": "14:00",
"duration": 30
},
"agentAdditionalData": {
"businessId": "biz_12345"
}
}
Expected Response:
{
"available": true,
"slots": [
{ "time": "14:00", "duration": 30 },
{ "time": "14:30", "duration": 30 },
{ "time": "15:00", "duration": 30 }
],
"message": "We have several slots available on January 20th."
}
Verifying Webhook Signatures
Always verify webhook signatures to ensure requests come from Dasha BlackBox.
Signature Verification Process
Dasha BlackBox includes an X-Dasha BlackBox-Signature header with each webhook:
X-Dasha BlackBox-Signature: sha256=abc123def456...
Verification Steps:
- Extract signature from header
- Compute HMAC-SHA256 of request body using your secret
- Compare computed signature with received signature
- Reject request if signatures don’t match
Implementation Examples
const crypto = require('crypto');
function verifyWebhookSignature(req, webhookSecret) {
// Get signature from header
const receivedSignature = req.headers['x-blackbox-signature'];
if (!receivedSignature) {
throw new Error('Missing signature header');
}
// Extract hash from "sha256=..." format
const receivedHash = receivedSignature.replace('sha256=', '');
// Compute expected signature
const payload = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha256', webhookSecret);
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 webhook signature');
}
return true;
}
// Usage in Express route
app.post('/webhook', (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: 'Invalid signature' });
}
});
import hmac
import hashlib
import json
def verify_webhook_signature(request, webhook_secret):
"""Verify HMAC signature of webhook request"""
# Get signature from header
received_signature = request.headers.get('X-Dasha BlackBox-Signature')
if not received_signature:
raise ValueError('Missing signature header')
# Extract hash from "sha256=..." format
received_hash = received_signature.replace('sha256=', '')
# Compute expected signature
payload = json.dumps(request.get_json())
expected_hash = hmac.new(
webhook_secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
# Timing-safe comparison
if not hmac.compare_digest(received_hash, expected_hash):
raise ValueError('Invalid webhook signature')
return True
# Usage in Flask route
@app.route('/webhook', 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': 'Invalid signature'}), 401
<?php
function verifyWebhookSignature($payload, $receivedSignature, $webhookSecret) {
// Extract hash from "sha256=..." format
$receivedHash = str_replace('sha256=', '', $receivedSignature);
// Compute expected signature
$expectedHash = hash_hmac('sha256', $payload, $webhookSecret);
// Timing-safe comparison
if (!hash_equals($expectedHash, $receivedHash)) {
throw new Exception('Invalid webhook signature');
}
return true;
}
// Usage
$payload = file_get_contents('php://input');
$receivedSignature = $_SERVER['HTTP_X_BLACKBOX_SIGNATURE'] ?? '';
$webhookSecret = getenv('WEBHOOK_SECRET');
try {
verifyWebhookSignature($payload, $receivedSignature, $webhookSecret);
// Process webhook
$data = json_decode($payload, true);
error_log('Valid webhook: ' . print_r($data, true));
http_response_code(200);
echo json_encode(['received' => true]);
} catch (Exception $e) {
error_log('Signature verification failed: ' . $e->getMessage());
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
}
?>
Testing Signature Verification
Test Valid Signature:
// Generate valid signature for testing
const crypto = require('crypto');
const payload = {
eventType: 'call.completed',
callId: 'call_test_123'
};
const secret = 'your-webhook-secret';
const payloadStr = JSON.stringify(payload);
const signature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payloadStr)
.digest('hex');
console.log('Valid signature:', signature);
// Test your endpoint
fetch('https://your-server.com/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Dasha BlackBox-Signature': signature
},
body: payloadStr
});
Test Invalid Signature:
// Send request with wrong signature
fetch('https://your-server.com/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Dasha BlackBox-Signature': 'sha256=invalid_signature_here'
},
body: JSON.stringify({
eventType: 'call.completed',
callId: 'call_test_123'
})
});
// Your endpoint should return 401 Unauthorized
Security Critical: Always verify signatures in production. Failing to verify signatures allows attackers to send fake webhooks to your endpoints.
Testing Retry Behavior
Dasha BlackBox automatically retries failed webhook deliveries. Test your endpoint’s retry handling.
Retry Configuration
Dasha BlackBox retries failed webhooks with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: After 1 minute
- Attempt 3: After 5 minutes
- Attempt 4: After 15 minutes
- Attempt 5: After 1 hour
Retry Triggers:
- HTTP status codes 500-599
- Connection timeouts (over 30 seconds)
- Network errors (DNS failures, connection refused)
No Retry:
- HTTP status codes 200-299 (success)
- HTTP status codes 400-499 (client errors)
Simulating Retry Scenarios
Test Temporary Failure:
// webhook-server.js
let attemptCount = 0;
app.post('/webhook', (req, res) => {
attemptCount++;
console.log(`Webhook attempt ${attemptCount}`);
// Fail first 2 attempts, succeed on 3rd
if (attemptCount <= 2) {
console.log('Simulating failure');
res.status(500).json({ error: 'Temporary server error' });
} else {
console.log('Processing webhook successfully');
res.status(200).json({ received: true });
}
});
Test Idempotency:
// Store processed webhook IDs to prevent duplicate processing
const processedWebhooks = new Set();
app.post('/webhook', (req, res) => {
const webhookId = req.body.callId + '_' + req.body.timestamp;
// Check if already processed
if (processedWebhooks.has(webhookId)) {
console.log('Duplicate webhook, already processed:', webhookId);
// Still return 200 to prevent retries
return res.status(200).json({
received: true,
duplicate: true
});
}
// Process webhook
processedWebhooks.add(webhookId);
console.log('Processing new webhook:', webhookId);
// Your business logic here
res.status(200).json({ received: true });
});
Test Timeout Handling:
// Simulate slow endpoint
app.post('/webhook-slow', async (req, res) => {
console.log('Received webhook, processing slowly...');
// Simulate 35 second processing (over 30s timeout)
await new Promise(resolve => setTimeout(resolve, 35000));
// This will trigger a timeout in Dasha BlackBox
res.status(200).json({ received: true });
});
Monitoring Retry Attempts
Add Retry Headers (if your server supports them):
app.post('/webhook', (req, res) => {
// Dasha BlackBox may send retry attempt number in custom header
const attemptNumber = req.headers['x-blackbox-attempt'] || 1;
console.log(`Processing webhook attempt ${attemptNumber}`);
// Process webhook
res.status(200).json({
received: true,
attemptNumber: parseInt(attemptNumber)
});
});
Testing Timeout Handling
Webhook endpoints must respond within 30 seconds. Test timeout scenarios.
Timeout Requirements
Response Time Limits:
- StartWebHookPayload: under 5 seconds (blocking call start)
- ToolWebHookPayload: under 10 seconds (blocking conversation)
- Other webhooks: under 30 seconds (non-blocking)
Testing Response Times
Measure Endpoint Performance:
// webhook-server.js with timing
app.post('/webhook', (req, res) => {
const startTime = Date.now();
// Process webhook
const eventType = req.body.eventType;
console.log(`Processing ${eventType}...`);
// Your business logic here
processWebhook(req.body);
const duration = Date.now() - startTime;
console.log(`Webhook processed in ${duration}ms`);
// Warn if approaching timeout
if (duration > 25000) {
console.warn('WARNING: Response time approaching 30s timeout!');
}
res.status(200).json({
received: true,
processingTime: duration
});
});
Optimize Slow Endpoints:
// BAD: Synchronous processing blocks response
app.post('/webhook-bad', (req, res) => {
// This might take over 30 seconds
const result = processLargeDataset(req.body);
updateDatabase(result);
sendNotifications(result);
res.status(200).json({ received: true });
});
// GOOD: Async processing with quick response
app.post('/webhook-good', (req, res) => {
// Respond immediately
res.status(200).json({ received: true });
// Process asynchronously
setImmediate(() => {
const result = processLargeDataset(req.body);
updateDatabase(result);
sendNotifications(result);
});
});
Use Queue for Heavy Processing:
// webhook-server.js with queue
const Queue = require('bull');
const webhookQueue = new Queue('webhooks');
// Quick response, queue processing
app.post('/webhook', async (req, res) => {
// Add to queue (fast operation)
await webhookQueue.add({
payload: req.body,
receivedAt: new Date().toISOString()
});
// Respond immediately
res.status(200).json({ received: true, queued: true });
});
// Process queue asynchronously
webhookQueue.process(async (job) => {
const { payload } = job.data;
// Heavy processing here (no time limit)
const result = await processWebhook(payload);
await updateDatabase(result);
await sendNotifications(result);
return result;
});
Debugging Webhook Deliveries
When webhooks fail, use these debugging techniques to identify the issue.
Common Webhook Failures
Connection Errors
Timeout Errors
HTTP Errors
Symptoms:
- “Connection refused”
- “DNS resolution failed”
- “SSL certificate error”
Solutions:
- Verify URL is correct and accessible
- Check firewall and security group rules
- Ensure SSL certificate is valid
- Test with
curl from external server
Symptoms:
- “Request timeout after 30 seconds”
- Partial webhook delivery
Solutions:
- Optimize endpoint performance
- Move heavy processing to background jobs
- Respond within 5 seconds for critical webhooks
- Monitor endpoint latency
Symptoms:
- 400 Bad Request
- 401 Unauthorized
- 500 Internal Server Error
Solutions:
- Check server logs for error details
- Verify request body parsing
- Ensure authentication is correct
- Add error handling and logging
Webhook Delivery Logs
Dasha BlackBox logs all webhook delivery attempts. Webhook delivery history and statistics are available in the dashboard only.
Dashboard:
- Navigate to Webhooks section
- Select webhook configuration
- Click Delivery Logs
- Filter by date, status, or event type
Webhook delivery logs are currently only accessible through the dashboard. There is no API endpoint for retrieving delivery history programmatically.
Debugging Checklist
Pre-Deployment Checklist:
Troubleshooting Steps:
- Test with webhook.site: Verify Dasha BlackBox sends webhooks correctly
- Test with ngrok: Verify your local handler works
- Check server logs: Look for errors in your application logs
- Verify signature: Ensure signature verification doesn’t reject valid requests
- Measure timing: Confirm response times are under limits
- Test retry: Verify idempotency and duplicate handling
- Check network: Ensure firewall/proxy allows Dasha BlackBox IPs
Production Testing Checklist
Before enabling webhooks in production, complete this comprehensive checklist.
Security Testing
Authentication & Authorization:
Input Validation:
Load Testing:
// Simulate high webhook volume
const loadTest = async () => {
const promises = [];
// Send 100 concurrent webhooks
for (let i = 0; i < 100; i++) {
promises.push(
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/webhook'
},
webhookType: 'CompletedWebHookPayload'
})
})
);
}
const results = await Promise.allSettled(promises);
const successes = results.filter(r => r.status === 'fulfilled').length;
const failures = results.filter(r => r.status === 'rejected').length;
console.log(`Load test complete: ${successes} succeeded, ${failures} failed`);
};
Performance Metrics:
Reliability Testing
Failure Scenarios:
Idempotency Testing:
// Test duplicate webhook handling
const testIdempotency = async () => {
const payload = {
webHook: {
url: 'https://your-server.com/webhook'
},
webhookType: 'CompletedWebHookPayload'
};
// Send same webhook 3 times
for (let i = 0; i < 3; i++) {
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(payload)
});
console.log(`Sent duplicate ${i + 1}`);
}
// Verify your server only processed it once
};
Monitoring & Alerting
Logging:
Alerts:
Documentation
Runbook:
Next Steps
After testing webhooks thoroughly:
- Enable in Production: Configure webhooks in production agents
- Monitor Deliveries: Watch webhook logs for issues
- Optimize Performance: Improve response times based on metrics
- Scale Infrastructure: Add capacity as webhook volume grows
Related Pages