Skip to main content

Overview

Webhooks allow you to receive real-time notifications about call lifecycle events. Instead of polling the API, Telepath pushes events to your application.

Setting Up Webhooks

Prerequisites

  • HTTPS endpoint that accepts POST requests
  • Ability to verify webhook signatures
  • Basic logging for debugging

Configure Webhook Endpoint

  1. Go to Telepath Dashboard
  2. Navigate to SettingsWebhooks
  3. Enter your HTTPS webhook URL
  4. Select events to receive
  5. Save

Webhook URL Requirements

  • Must be publicly accessible
  • Must use HTTPS (not HTTP)
  • Must accept POST requests
  • Should respond with 2xx status code
  • Should process within 30 seconds
  • May receive up to 100 concurrent requests

Webhook Events

Call Started

Sent when a call connects through Telepath.
{
  "event": "call.started",
  "call_id": "call_abc123xyz",
  "timestamp": "2024-03-05T10:30:00Z",
  "from": "+1-555-123-4567",
  "to": "+1-555-987-6543",
  "connection_id": "conn_def456abc",
  "carrier": "twilio"
}
Fields:
  • call_id: Unique call identifier
  • timestamp: ISO 8601 timestamp
  • from: Caller’s phone number
  • to: Destination/your phone number
  • connection_id: Which connection handled the call
  • carrier: Source carrier (if available)

Call Ended

Sent when a call disconnects.
{
  "event": "call.ended",
  "call_id": "call_abc123xyz",
  "timestamp": "2024-03-05T10:35:45Z",
  "duration_seconds": 345,
  "reason": "normal_disconnect",
  "metrics": {
    "carrier_lag_ms": 75,
    "ai_latency_ms": 150,
    "gateway_processing_ms": 25
  }
}
Fields:
  • call_id: Unique call identifier
  • timestamp: When call ended
  • duration_seconds: Total call length
  • reason: How call ended (see below)
  • metrics: Performance data
Disconnect Reasons:
  • normal_disconnect: User hung up
  • no_answer: Call went to voicemail/timeout
  • connection_failed: Could not connect to AI agent
  • network_error: Network connectivity issue
  • ai_provider_error: AI provider returned error
  • carrier_error: Carrier-side issue
  • unknown: Unknown reason

AI Agent Connected

Sent when AI agent successfully connects.
{
  "event": "agent.connected",
  "call_id": "call_abc123xyz",
  "timestamp": "2024-03-05T10:30:15Z",
  "agent_type": "openai",
  "agent_id": "agent_user_123"
}
Fields:
  • agent_type: Which provider (openai, elevenlabs, custom)
  • agent_id: Specific agent identifier

AI Agent Disconnected

Sent when connection to AI agent drops.
{
  "event": "agent.disconnected",
  "call_id": "call_abc123xyz",
  "timestamp": "2024-03-05T10:32:30Z",
  "reason": "timeout",
  "error_message": "Agent did not respond within 5 seconds"
}
Fields:
  • reason: Why disconnect occurred
  • error_message: Detailed error description

Error Occurred

Sent when an error happens during the call.
{
  "event": "error.occurred",
  "call_id": "call_abc123xyz",
  "timestamp": "2024-03-05T10:31:00Z",
  "error_code": "AGENT_AUTH_FAILED",
  "error_message": "OpenAI API key is invalid",
  "severity": "critical"
}
Fields:
  • error_code: Machine-readable error code
  • error_message: Human-readable description
  • severity: critical, warning, info

Processing Webhooks

Handling the Request

from flask import Flask, request
import json
import hmac
import hashlib

app = Flask(__name__)

@app.route('/telepath-webhook', methods=['POST'])
def handle_webhook():
    # Verify webhook signature (see below)
    if not verify_signature(request):
        return {'error': 'Invalid signature'}, 401

    # Get event data
    event = request.json

    # Process based on event type
    if event['event'] == 'call.started':
        handle_call_started(event)
    elif event['event'] == 'call.ended':
        handle_call_ended(event)
    elif event['event'] == 'agent.connected':
        handle_agent_connected(event)
    elif event['event'] == 'error.occurred':
        handle_error(event)

    # Always return 200 to acknowledge receipt
    return {'success': True}, 200

def handle_call_started(event):
    call_id = event['call_id']
    print(f"Call started: {call_id}")
    # Your logic here

def handle_call_ended(event):
    call_id = event['call_id']
    duration = event['duration_seconds']
    print(f"Call {call_id} ended after {duration}s")
    # Your logic here

def handle_agent_connected(event):
    print(f"Agent connected for call {event['call_id']}")
    # Your logic here

def handle_error(event):
    print(f"Error: {event['error_message']}")
    # Your error handling logic

Verify Webhook Signature

Each webhook includes a signature header you should verify. Header: X-Telepath-Signature Format: sha256=<hash>
def verify_signature(request):
    signature = request.headers.get('X-Telepath-Signature', '')

    # Get your webhook secret from dashboard
    webhook_secret = "your_webhook_secret"

    # Get raw request body
    body = request.get_data()

    # Calculate expected signature
    expected = 'sha256=' + hmac.new(
        webhook_secret.encode(),
        body,
        hashlib.sha256
    ).hexdigest()

    # Compare (constant-time comparison)
    return hmac.compare_digest(signature, expected)

Best Practices

Always Respond:
# Good
return {'success': True}, 200

# Bad - will retry
return None, 500
Process Asynchronously:
# Good - respond immediately, process later
queue.enqueue(handle_webhook, event)
return {'success': True}, 200

# Bad - slow response time
process_webhook(event)  # Takes 5 seconds
return {'success': True}, 200
Handle Duplicates:
# Check if we already processed this call_id
if webhook_received(event['call_id']):
    return {'success': True}, 200  # Already processed

# Process new event
process_webhook(event)
mark_webhook_received(event['call_id'])
return {'success': True}, 200
Log Everything:
import logging

logger = logging.getLogger(__name__)

@app.route('/telepath-webhook', methods=['POST'])
def handle_webhook():
    event = request.json
    logger.info(f"Received webhook: {json.dumps(event)}")

    try:
        process_event(event)
        logger.info(f"Successfully processed {event['call_id']}")
    except Exception as e:
        logger.error(f"Error processing webhook: {e}")
        # Still return 200 to prevent retries

    return {'success': True}, 200

Retry Logic

If your endpoint returns a non-2xx status or times out:
  • Retry 1: After 5 seconds
  • Retry 2: After 30 seconds
  • Retry 3: After 5 minutes
  • Retry 4: After 30 minutes
  • Retry 5: After 2 hours
After 5 failed attempts, the webhook is marked as failed and not retried further.

Testing Webhooks

Test from Dashboard

  1. Go to SettingsWebhooks
  2. Click Test next to your webhook URL
  3. Select an event type
  4. Click Send Test Event
  5. Check your logs for receipt

Using Webhook Debugging Tools

Use services like Webhook.cool or RequestBin to inspect webhook payloads:
  1. Create a temporary webhook URL
  2. Configure it in Telepath
  3. Make a test call
  4. View the request details

Local Testing with ngrok

For local development:
# Start ngrok tunnel
ngrok http 5000

# Use the ngrok URL in Telepath webhook settings
# https://abc123.ngrok.io/telepath-webhook

Common Use Cases

Logging to Database

from datetime import datetime

def handle_call_ended(event):
    # Store call information
    call_record = {
        'call_id': event['call_id'],
        'duration': event['duration_seconds'],
        'carrier_lag': event['metrics']['carrier_lag_ms'],
        'ai_latency': event['metrics']['ai_latency_ms'],
        'timestamp': event['timestamp']
    }
    db.calls.insert_one(call_record)

Triggering Actions

def handle_call_ended(event):
    # Send notification if call was long
    if event['duration_seconds'] > 600:  # 10+ minutes
        notify_team(f"Long call: {event['call_id']}")

    # Track errors
    if event['reason'] == 'ai_provider_error':
        log_error(event['call_id'], "AI provider error")

Real-time Dashboards

def handle_call_started(event):
    # Broadcast to dashboard via WebSocket
    broadcast_to_dashboard({
        'type': 'call_started',
        'data': event
    })

def handle_call_ended(event):
    broadcast_to_dashboard({
        'type': 'call_ended',
        'data': event
    })

Integration with External Systems

def handle_call_ended(event):
    # Send to Slack
    slack.post_message(
        f"Call {event['call_id']} ended after {event['duration_seconds']}s"
    )

    # Send to data warehouse
    warehouse.insert(table='calls', data=event)

Monitoring Webhooks

Check Webhook Status

  1. Go to SettingsWebhooks
  2. View delivery status:
    • ✅ All recent events delivered
    • ⚠️ Some events failed
    • ❌ Endpoint unreachable

Review Failed Webhooks

  1. Click View Logs
  2. Filter by status
  3. See error messages
  4. Retry failed deliveries

Webhook Metrics

Available via API:
  • Total webhooks sent
  • Delivery success rate
  • Average delivery time
  • Failures by error type
See API Reference for details.

Troubleshooting

Webhooks Not Received

Check:
  • Endpoint is publicly accessible
  • Using HTTPS, not HTTP
  • Firewall allows inbound POST
  • Endpoint accepts on correct path
Test:
  • Send test webhook from dashboard
  • Check logs for any errors
  • Verify signature validation

Duplicate Events

Solution:
  • Implement idempotency using call_id
  • Store received event IDs
  • Skip if already processed

Slow Processing

Solution:
  • Move processing to background queue
  • Return 200 immediately
  • Process async to prevent timeouts

High Latency

Check:
  • Endpoint response time
  • Database query performance
  • External API calls
  • Network conditions