Webhooks
Receive real-time call lifecycle notifications
Overview
Webhooks allow your application to receive real-time notifications about call lifecycle events — no polling required. When a call starts, ends, or encounters an error, Telepath sends an HTTP POST request to your configured endpoint with a JSON payload containing the event data.
Setting Up Webhooks
Prerequisites
- A publicly accessible HTTPS endpoint that can receive POST requests
- The ability to verify HMAC-SHA256 signatures to authenticate webhook payloads
- Logging infrastructure to record webhook events for debugging
Configure Webhook Endpoint
Log in to the Telepath Dashboard
Navigate to Settings → Webhooks
Click Add Webhook Endpoint
Enter your HTTPS endpoint URL and select which event types to receive
Click Save and copy the generated webhook signing secret
Webhook URL Requirements
- Must be publicly accessible from the internet
- Must use HTTPS (TLS) — plain HTTP endpoints are rejected
- Must accept HTTP POST requests
- Must return a 2xx HTTP status code to acknowledge receipt
- Must respond within 30 seconds (requests that timeout are retried)
- Should be able to handle up to 100 concurrent webhook deliveries
Webhook Events
Call Started
Fired immediately when an inbound call is accepted by Telepath.
{
"event": "call.started",
"call_id": "call_01abc123xyz",
"connection_id": "conn_01def456uvw",
"timestamp": "2026-03-10T14:22:31.842Z",
"caller_id": "+14155550100",
"called_number": "+18005551234",
"carrier": "twilio",
"codec": "G722"
}
Fields: event, call_id, connection_id, timestamp (ISO 8601 UTC), caller_id (E.164), called_number (E.164), carrier, codec.
Call Ended
Fired when a call terminates, including full performance metrics.
{
"event": "call.ended",
"call_id": "call_01abc123xyz",
"connection_id": "conn_01def456uvw",
"timestamp": "2026-03-10T14:25:47.213Z",
"duration_seconds": 196,
"disconnect_reason": "caller_hangup",
"metrics": {
"carrier_lag_ms": 82,
"ai_latency_ms": 187,
"gateway_processing_ms": 14,
"packet_loss_pct": 0.2
}
}
Fields: all fields from call.started plus duration_seconds, metrics object, and disconnect_reason.
Disconnect reasons: caller_hangup, agent_hangup, timeout, ai_error, carrier_error, sip_error.
AI Agent Connected
Fired when Telepath successfully establishes a WebSocket connection to your AI provider.
{
"event": "ai_agent.connected",
"call_id": "call_01abc123xyz",
"connection_id": "conn_01def456uvw",
"timestamp": "2026-03-10T14:22:31.991Z",
"provider": "openai",
"model": "gpt-4o-realtime-preview"
}
Fields: event, call_id, connection_id, timestamp, provider, model.
AI Agent Disconnected
Fired when the WebSocket connection to your AI provider closes, either normally or due to an error.
{
"event": "ai_agent.disconnected",
"call_id": "call_01abc123xyz",
"connection_id": "conn_01def456uvw",
"timestamp": "2026-03-10T14:25:47.150Z",
"reason": "normal_closure",
"provider": "openai"
}
Fields: event, call_id, connection_id, timestamp, reason (normal_closure, error, timeout), provider.
Error Occurred
Fired when a non-fatal error occurs during a call (e.g., a transient AI provider error that is retried).
{
"event": "error.occurred",
"call_id": "call_01abc123xyz",
"connection_id": "conn_01def456uvw",
"timestamp": "2026-03-10T14:23:14.007Z",
"error_code": "AI_PROVIDER_TIMEOUT",
"error_message": "AI provider did not respond within 5000ms",
"severity": "warning"
}
Fields: event, call_id, connection_id, timestamp, error_code, error_message, severity (warning or error).
Processing Webhooks
Handling the Request
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_signing_secret"
@app.route("/webhook/telepath", methods=["POST"])
def handle_webhook():
# Verify signature
signature = request.headers.get("X-Telepath-Signature", "")
payload = request.get_data()
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(f"sha256={expected}", signature):
return jsonify({"error": "Invalid signature"}), 401
# Parse event
event = request.get_json()
event_type = event.get("event")
if event_type == "call.started":
handle_call_started(event)
elif event_type == "call.ended":
handle_call_ended(event)
elif event_type == "ai_agent.connected":
handle_ai_connected(event)
elif event_type == "error.occurred":
handle_error(event)
# Always respond 200 quickly
return jsonify({"received": True}), 200
def handle_call_started(event):
print(f"Call started: {event['call_id']} from {event['caller_id']}")
def handle_call_ended(event):
print(f"Call ended: {event['call_id']}, duration: {event['duration_seconds']}s")
def handle_ai_connected(event):
print(f"AI connected for call: {event['call_id']}, provider: {event['provider']}")
def handle_error(event):
print(f"Error on call {event['call_id']}: {event['error_message']}")
Verify Webhook Signature
Every webhook request includes an X-Telepath-Signature header containing an HMAC-SHA256 signature of the raw request body, prefixed with sha256=. Always verify this signature before processing the payload.
import hmac
import hashlib
def verify_signature(payload_bytes, signature_header, secret):
"""Returns True if the signature is valid."""
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
payload_bytes,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
Best Practices
Always Respond Quickly — Return a 200 response immediately, before processing the event. If your handler takes more than 30 seconds, Telepath will mark the delivery as failed and retry.
@app.route("/webhook/telepath", methods=["POST"])
def handle_webhook():
event = request.get_json()
# Queue for async processing
task_queue.enqueue(process_event, event)
# Respond immediately
return jsonify({"received": True}), 200
Process Asynchronously — Use a queue (Celery, RQ, SQS, etc.) to process events asynchronously so your HTTP handler returns in milliseconds.
Handle Duplicates — Store processed call_id + event combinations to detect and skip duplicate deliveries.
def process_event(event):
key = f"{event['call_id']}:{event['event']}"
if redis_client.setnx(f"webhook:{key}", 1):
redis_client.expire(f"webhook:{key}", 86400)
# Process the event (only runs once)
handle_event(event)
else:
print(f"Duplicate event skipped: {key}")
Log Everything — Store the raw webhook payload, timestamp, and processing result for every event to aid debugging.
Retry Logic
If your endpoint returns a non-2xx status code or does not respond within 30 seconds, Telepath retries delivery with exponential backoff:
- Attempt 1: immediately
- Attempt 2: after 5 seconds
- Attempt 3: after 30 seconds
- Attempt 4: after 5 minutes
- Attempt 5: after 30 minutes
- Attempt 6: after 2 hours
After 5 consecutive failures, the delivery is marked as failed and no further retries are attempted. Failed deliveries are visible in Settings → Webhooks → Failed Deliveries.
Testing Webhooks
Test from Dashboard
Navigate to Settings → Webhooks
Click Send Test Event next to your endpoint
Select an event type from the dropdown (e.g.,
call.started)Click Send
Verify the test event appears in your server's logs
Using Webhook Debugging Tools
During development, tools like webhook.cool or RequestBin provide temporary HTTPS endpoints that display incoming webhook payloads in the browser, letting you inspect the raw data without running a local server.
Local Testing with ngrok
# Install ngrok and expose your local server
ngrok http 5000
# ngrok will display a public HTTPS URL like:
# https://abc123.ngrok.io -> http://localhost:5000
# Use this URL as your Telepath webhook endpoint
Common Use Cases
Logging to Database
def handle_call_ended(event):
db.execute("""
INSERT INTO call_records
(call_id, duration, carrier_lag, ai_latency, disconnect_reason, created_at)
VALUES (?, ?, ?, ?, ?, ?)
""", (
event["call_id"],
event["duration_seconds"],
event["metrics"]["carrier_lag_ms"],
event["metrics"]["ai_latency_ms"],
event["disconnect_reason"],
event["timestamp"]
))
Triggering Actions
def handle_call_ended(event):
if event["disconnect_reason"] == "ai_error":
# Alert on-call engineer
pagerduty.trigger_incident(
summary=f"AI error on call {event['call_id']}",
severity="warning"
)
elif event["duration_seconds"] > 600:
# Flag long calls for review
crm.flag_for_review(call_id=event["call_id"])
Real-time Dashboards
def handle_call_started(event):
# Push to real-time dashboard via WebSocket
socketio.emit("call_started", {
"call_id": event["call_id"],
"caller": event["caller_id"],
"connection": event["connection_id"]
}, room="dashboard")
Integration with External Systems
def handle_call_ended(event):
# Update CRM with call outcome
crm.update_contact_activity(
caller_id=event.get("caller_id"),
activity_type="voice_ai_call",
duration=event["duration_seconds"],
metadata=event["metrics"]
)
# Post summary to Slack
slack.post_message(
channel="#sales-calls",
text=f"Call completed: {event['duration_seconds']}s, "
f"latency: {event['metrics']['ai_latency_ms']}ms"
)
Monitoring Webhooks
Check Webhook Status
In Settings → Webhooks, each endpoint shows one of three status indicators:
- Healthy (green) — all recent deliveries succeeded
- Degraded (yellow) — some deliveries failed but retries succeeded
- Failed (red) — multiple consecutive delivery failures
Review Failed Webhooks
Click Failed Deliveries on your endpoint to see the list of failed events, the HTTP response code received, and the number of retry attempts made. You can manually retry individual failed deliveries.
Webhook Metrics
- Total deliveries in the last 24h / 7d / 30d
- Success rate percentage
- Average delivery latency (time from event to your endpoint's response)
- Count of retried deliveries
Troubleshooting
Webhooks Not Received
Check:
- Your endpoint URL is correct and uses HTTPS
- Your server is publicly accessible (not behind a firewall or on localhost)
- The webhook endpoint is enabled in the Telepath dashboard
- You have selected the correct event types
Test: Use Send Test Event in the dashboard and check if it appears in your server logs. If it does, the webhook is working and the issue is with event filtering.
Duplicate Events
Telepath's retry logic may deliver the same event more than once. Implement idempotency using the call_id + event combination as a unique key. Check each event against a deduplication store (Redis, database) before processing.
Slow Processing
If your handler takes longer than 30 seconds, Telepath will retry and you will receive duplicate events. Offload all business logic to a background queue and return 200 immediately from the HTTP handler.
High Latency
Review the delivery latency metric for your endpoint in the Webhooks settings. If latency is consistently high:
- Ensure your server is deployed close to Telepath's infrastructure (US region)
- Check for database or external API calls blocking your HTTP handler
- Move processing to an async queue and return 200 immediately
- Scale your webhook receiver horizontally if you receive high call volume