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

  1. Log in to the Telepath Dashboard

  2. Navigate to Settings → Webhooks

  3. Click Add Webhook Endpoint

  4. Enter your HTTPS endpoint URL and select which event types to receive

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

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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).

json
{
  "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

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

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

python
@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.

python
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

  1. Navigate to Settings → Webhooks

  2. Click Send Test Event next to your endpoint

  3. Select an event type from the dropdown (e.g., call.started)

  4. Click Send

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

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

python
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

python
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

python
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

python
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