Skip to main content
The error event is triggered when critical errors occur during call processing, specifically when Large Language Model (LLM) or Speech-to-Text (STT) services fail.
Error events indicate technical failures that may cause conversation interruptions or call termination. These require immediate attention for maintaining service quality.

When It’s Triggered

This event is sent when:
  • LLM Errors: Language model API failures, timeouts, or service unavailability
  • STT Errors: Speech-to-text conversion failures or transcription service issues
  • Authentication Issues: API key problems or authorization failures
  • Quota Exceeded: Service limits reached for LLM or STT providers
  • Network Errors: Connectivity issues affecting external service calls

Error Categories

LLM Errors

  • Provider API downtime or maintenance
  • Rate limiting exceeded
  • Model capacity limitations
  • Invalid API keys or expired tokens
  • Malformed requests or unsupported parameters
  • Content filtering or safety violations

STT Errors

  • Audio quality too poor for transcription
  • Unsupported audio format or codec
  • Service provider outages
  • Network connectivity issues
  • Authentication or quota problems
  • Language detection failures

Event Structure

{
    "message": {
        "timestamp": 1772702495000,
        "type": "error",
        "call": { /* Call Object */ },
        "assistant": { /* Assistant Object */ },
        "messages": [ /* Conversation until error */ ],
        "phone": { /* Phone Object */ },
        "customer": { /* Customer Object */ },
        "analysis": { /* Empty during error */ },
        "error": { /* Error Details */ }
    }
}

Key Fields

FieldTypeDescription
message.typestringAlways “error” for this event
message.timestampnumberUnix timestamp when error occurred
error.typestringError category: “llm_error” or “stt_error”
error.codestringSpecific error code from provider
error.messagestringHuman-readable error description
error.providerstringService provider that failed
error.retryablebooleanWhether the error can be retried

Call Object

The call object contains comprehensive information about the call session.
FieldTypeDescription
idstringUnique call identifier (e.g., “WC-82015760-c3bd-427d-a23b-ba9b07e4ab85”)
teamIdstringOrganization/team identifier
assistantIdstringID of the assistant handling this call
callTypestringType of call: “web”, “phone”, etc.
directionstringCall direction: “inbound” or “outbound”
startAtstringISO timestamp when call started
endAtstringISO timestamp when call ended (only in end-of-call-report)
userNumberstringUser’s phone number or identifier
assistantNumberstringAssistant’s number or identifier
statusstringCurrent call status: “queued”, “ongoing”, “finished”, “forwarded”
phoneCallStatusstringDetailed phone status: “in-progress”, “completed”, etc.
phoneCallStatusReasonstringHuman-readable status reason
callEndTriggerBystringWhat triggered call end: “bot”, “user”, “system”
assistantCallDurationnumberDuration of call in milliseconds
analysisanalysis-objectCall analysis results
recordingobjectRecording information with S3 bucket and path
assistantOverridesobjectDynamic variables and validation overrides
metadataobjectCustom metadata associated with the call
costobjectCost breakdown (only in end-of-call-report)
metricsobjectDetailed call metrics (only in end-of-call-report)

Assistant Object

The assistant object contains the configuration and settings of the assistant handling the call.
In some webhook events, the assistant object may be truncated for brevity. The full assistant configuration is typically included in status-update events.
FieldTypeDescription
_idstringUnique assistant identifier
namestringDisplay name of the assistant
welcomeMessagestringMessage played when call starts
welcomeMessageModestringHow welcome message is triggered: “automatic”, “manual”
welcomeMessageInterruptionsEnabledbooleanWhether users can interrupt welcome message
endCallMessagestringMessage played when call ends
endCallPhrasesarrayPhrases that trigger call termination
bargeInEnabledbooleanWhether users can interrupt assistant responses
assistantProviderstringLLM provider: “openai”, “anthropic”, “gemini”, etc.
assistantModelstringSpecific model being used
assistantSystemPromptstringSystem prompt defining assistant behavior
assistantTemperaturenumberLLM creativity setting (0.0 to 1.0)
assistantMaxTokensnumberMaximum tokens per response
assistantAnalysisobjectConfiguration for call analysis features
assistantServerobjectWebhook configuration for this assistant
configobjectSpeech-to-text and text-to-speech configurations

Key Subobjects

  • assistantAnalysis: Contains settings for summary generation, success evaluation, and structured data extraction
  • assistantServer: The webhook configuration that triggered this event
  • config.speech: STT/TTS vendor settings, voice configuration, and language options

Messages Object

The messages array contains the conversation history between the user and assistant.
Each conversation-update event includes all messages from the start of the call up to that point, not just the latest message. This ensures that if any individual event is missed, you still have the complete conversation context.

Message Structure

FieldTypeDescription
messageIdstringUnique message identifier
rolestringMessage sender: “user” or “assistant”
textstringTranscribed or generated message content
timestampnumberUnix timestamp in milliseconds
metricsobjectPerformance metrics for this message
skippedAssistantMessagesarrayAssistant messages that were skipped due to interruptions

Message Metrics

Each message includes detailed performance metrics:

Timeline Metrics

  • totalElapsedTimeMs: Total time since call start
  • offsetFromPreviousTurnMs: Time gap from previous message

Audio Metrics

  • totalAudioReceivedMs: Total audio duration received
  • audioDelayPerTurnMs: Audio processing delay for this turn
  • delayedPacketsPerTurnCount: Count of delayed network packets

Speech-to-Text (STT) Metrics

  • timestampMs: When transcription was completed
  • startOffsetMs/endOffsetMs: Audio segment boundaries
  • confidence: Transcription confidence score (0.0 to 1.0)
  • vadMs: Voice activity detection duration

Text-to-Speech (TTS) Metrics

  • audioDurationMs: Duration of generated audio
  • generationTimeMs: Time to generate audio
  • queueLatencyMs: Processing queue delay
  • isCachePlaying: Whether audio was served from cache

LLM Metrics

  • responseLatencyMs: Time for LLM to generate response
  • queueLatencyMs: Processing queue delay

Message Types

  • Welcome Messages: Have messageId starting with “welcome-”
  • User Messages: Have messageId starting with “user-”
  • Assistant Messages: Have complex messageId with user reference and timestamp

Example Payloads

{
  "message": {
    "timestamp": 1772702495000,
    "type": "error",
    "call": {
      "id": "WC-82015760-c3bd-427d-a23b-ba9b07e4ab85",
      "status": "ongoing",
      "assistantId": "697769ef5e6d94d5ad83e01e"
    },
    "assistant": {
      "_id": "697769ef5e6d94d5ad83e01e",
      "name": "Mary Dental - main",
      "assistantProvider": "openai",
      "assistantModel": "gpt-4"
    },
    "error": {
      "type": "llm_error",
      "code": "rate_limit_exceeded",
      "message": "Rate limit exceeded for organization",
      "provider": "openai",
      "retryable": true,
      "details": {
        "limit_type": "requests_per_minute",
        "current_usage": 150,
        "limit": 100
      }
    },
    "messages": [
      {
        "messageId": "user-1",
        "role": "user",
        "text": "I need to schedule an appointment",
        "timestamp": 1772702490000
      }
    ]
  }
}

Error Handling Strategies

Automatic Retry Logic

def handle_error_event(event_data):
    error = event_data["message"]["error"]
    call = event_data["message"]["call"]

    if error["retryable"]:
        # Implement exponential backoff
        retry_count = get_retry_count(call["id"], error["type"])

        if retry_count < MAX_RETRIES:
            delay = min(2 ** retry_count, MAX_DELAY)
            schedule_retry(call["id"], delay, error["type"])

            logger.warning(f"Retrying {error['type']} for call {call['id']} "
                          f"in {delay}s (attempt {retry_count + 1})")
        else:
            # Max retries exceeded - escalate
            escalate_error(call["id"], error)
    else:
        # Non-retryable error - immediate escalation
        escalate_error(call["id"], error)

Provider Failover

def implement_provider_failover(event_data):
    error = event_data["message"]["error"]
    call = event_data["message"]["call"]
    assistant = event_data["message"]["assistant"]

    if error["type"] == "llm_error":
        # Switch to backup LLM provider
        current_provider = assistant["assistantProvider"]

        fallback_providers = {
            "openai": "anthropic",
            "anthropic": "google",
            "google": "openai"
        }

        backup_provider = fallback_providers.get(current_provider)
        if backup_provider:
            switch_llm_provider(call["id"], backup_provider)
            logger.info(f"Switched call {call['id']} from {current_provider} to {backup_provider}")

    elif error["type"] == "stt_error":
        # Switch to backup STT provider
        switch_stt_provider(call["id"], "deepgram_nova")
        logger.info(f"Switched STT provider for call {call['id']}")

Graceful Degradation

def handle_graceful_degradation(event_data):
    error = event_data["message"]["error"]
    call = event_data["message"]["call"]

    if error["type"] == "llm_error":
        # Use pre-defined fallback responses
        fallback_responses = [
            "I apologize for the technical difficulty. Let me connect you with a human agent.",
            "I'm experiencing a temporary issue. Please hold while I transfer you.",
            "Due to a technical problem, I'll need to forward your call to our support team."
        ]

        response = random.choice(fallback_responses)
        send_fallback_message(call["id"], response)
        initiate_human_handoff(call["id"])

    elif error["type"] == "stt_error":
        # Ask user to speak more clearly
        send_clarification_message(call["id"],
            "I'm having trouble hearing you clearly. Could you please repeat that?")

Real-time Alerting

def alert_on_error(event_data):
    error = event_data["message"]["error"]
    call = event_data["message"]["call"]

    alert_severity = determine_severity(error)

    alert_data = {
        "alert_type": "service_error",
        "severity": alert_severity,
        "error_type": error["type"],
        "error_code": error["code"],
        "provider": error["provider"],
        "call_id": call["id"],
        "assistant_id": call["assistantId"],
        "retryable": error["retryable"],
        "timestamp": event_data["message"]["timestamp"]
    }

    if alert_severity == "critical":
        send_immediate_alert(alert_data)
        page_on_call_engineer(alert_data)
    else:
        log_error_for_analysis(alert_data)

def determine_severity(error):
    # Critical: Authentication failures, quota exceeded
    # High: Provider outages, repeated failures
    # Medium: Temporary network issues
    # Low: Individual request failures

    critical_codes = ["authentication_failed", "quota_exceeded", "service_unavailable"]
    if error["code"] in critical_codes:
        return "critical"
    elif not error["retryable"]:
        return "high"
    else:
        return "medium"

Error Analysis and Monitoring

Error Rate Tracking

def track_error_metrics(event_data):
    error = event_data["message"]["error"]
    call = event_data["message"]["call"]

    # Track error rates by provider
    metrics.increment("errors.total", tags={
        "error_type": error["type"],
        "provider": error["provider"],
        "error_code": error["code"],
        "assistant_id": call["assistantId"]
    })

    # Track error distribution over time
    hour = datetime.fromtimestamp(
        event_data["message"]["timestamp"] / 1000
    ).hour

    metrics.increment("errors.by_hour", tags={
        "hour": hour,
        "error_type": error["type"]
    })

Root Cause Analysis

def analyze_error_patterns():
    # Get recent errors
    recent_errors = db.error_events.find({
        "timestamp": {"$gte": datetime.utcnow() - timedelta(hours=24)}
    })

    analysis = {
        "error_spike": detect_error_spikes(recent_errors),
        "provider_health": assess_provider_health(recent_errors),
        "assistant_issues": find_problematic_assistants(recent_errors),
        "temporal_patterns": analyze_time_patterns(recent_errors)
    }

    # Generate actionable insights
    for insight in generate_insights(analysis):
        if insight["confidence"] > 0.8:
            create_incident_ticket(insight)

def detect_error_spikes(errors):
    # Group errors by 10-minute windows
    windows = defaultdict(int)
    for error in errors:
        window = error["timestamp"] // (10 * 60 * 1000) * (10 * 60 * 1000)
        windows[window] += 1

    # Detect unusual spikes
    values = list(windows.values())
    if values:
        mean = statistics.mean(values)
        stdev = statistics.stdev(values) if len(values) > 1 else 0
        threshold = mean + (2 * stdev)

        spikes = [(k, v) for k, v in windows.items() if v > threshold]
        return spikes
    return []

Error Prevention

Proactive Monitoring

# Set up early warning systems
MONITORING_THRESHOLDS = {
    "llm_error_rate": {
        "warning": 0.02,   # 2% error rate
        "critical": 0.05,  # 5% error rate
        "window": "5m"
    },
    "stt_error_rate": {
        "warning": 0.01,   # 1% error rate
        "critical": 0.03,  # 3% error rate
        "window": "5m"
    },
    "provider_response_time": {
        "warning": 3000,   # 3 seconds
        "critical": 5000,  # 5 seconds
        "window": "1m"
    }
}

Configuration Optimization

{
    "assistantProvider": "openai",
    "assistantModel": "gpt-4o-mini",  // More reliable than newer models
    "assistantMaxTokens": 150,        // Shorter responses = less prone to errors
    "assistantTemperature": 0.3,      // Lower temperature = more consistent
    "config": {
        "speech": {
            "stt": {
                "vendor": "deepgram",
                "model": "nova-2",      // Stable, well-tested model
                "profanityFilter": true,
                "hints": ["appointment", "doctor", "clinic"]  // Domain-specific hints
            }
        }
    }
}

Error Recovery Testing

def test_error_scenarios():
    # Simulate various error conditions
    test_scenarios = [
        {"type": "rate_limit", "provider": "openai"},
        {"type": "auth_failure", "provider": "anthropic"},
        {"type": "audio_quality", "provider": "deepgram"},
        {"type": "network_timeout", "provider": "elevenlabs"}
    ]

    for scenario in test_scenarios:
        result = simulate_error_scenario(scenario)
        assert_error_handling_works(result)

User Communication

When errors occur, transparent communication improves user experience:
ERROR_USER_MESSAGES = {
    "llm_error": [
        "I'm experiencing a brief technical issue. Please give me a moment.",
        "Let me think about that - I'm processing your request."
    ],
    "stt_error": [
        "I didn't catch that clearly. Could you please repeat?",
        "I'm having trouble hearing you. Could you speak a bit louder?"
    ],
    "quota_exceeded": [
        "I'm currently at capacity. Let me transfer you to a human agent.",
        "Due to high demand, I'll connect you with our support team."
    ]
}

Related Events

Error events may be preceded by hang events if processing delays occur before the error

Error Event Examples

This document contains examples of different types of error webhook events.

LLM Rate Limit Error

{
    "message": {
        "timestamp": 1772702495000,
        "type": "error",
        "call": {
            "id": "WC-82015760-c3bd-427d-a23b-ba9b07e4ab85",
            "teamId": "67c0231ae6880fe48ef929ee",
            "assistantId": "697769ef5e6d94d5ad83e01e",
            "callType": "web",
            "direction": "inbound",
            "startAt": "2026-03-05T09:21:20.063Z",
            "userNumber": "web-Ramesh Naik",
            "assistantNumber": "697769ef5e6d94d5ad83e01e",
            "status": "ongoing",
            "phoneCallStatus": "in-progress",
            "phoneCallStatusReason": "Call is in progress",
            "callEndTriggerBy": "",
            "assistantCallDuration": 15000,
            "analysis": {
                "summary": "",
                "successEvaluation": ""
            },
            "recording": {
                "s3Bucket": "",
                "path": ""
            },
            "assistantOverrides": {},
            "metadata": {}
        },
        "assistant": {
            "_id": "697769ef5e6d94d5ad83e01e",
            "name": "Mary Dental - main",
            "assistantProvider": "openai",
            "assistantModel": "gpt-4",
            "assistantSystemPrompt": "You here are a voice assistant for Apollo clinic..."
        },
        "messages": [
            {
                "messageId": "welcome-1",
                "role": "assistant",
                "text": "Welcome to Apollo clinic!!",
                "timestamp": 1772702480279
            },
            {
                "messageId": "user-1",
                "role": "user",
                "text": "I need to schedule an appointment please",
                "timestamp": 1772702490000
            }
        ],
        "phone": {
            "provider": {
                "name": ""
            }
        },
        "customer": {
            "number": "web-Ramesh Naik"
        },
        "analysis": {},
        "error": {
            "type": "llm_error",
            "code": "rate_limit_exceeded",
            "message": "Rate limit exceeded for organization",
            "provider": "openai",
            "retryable": true,
            "details": {
                "limit_type": "requests_per_minute",
                "current_usage": 150,
                "limit": 100,
                "reset_time": "2026-03-05T09:23:00Z"
            }
        }
    }
}

STT Audio Quality Error

{
    "message": {
        "timestamp": 1772702485000,
        "type": "error",
        "call": {
            "id": "WC-82015760-c3bd-427d-a23b-ba9b07e4ab85",
            "teamId": "67c0231ae6880fe48ef929ee",
            "assistantId": "697769ef5e6d94d5ad83e01e",
            "status": "ongoing"
        },
        "assistant": {
            "_id": "697769ef5e6d94d5ad83e01e",
            "name": "Mary Dental - main"
        },
        "messages": [
            {
                "messageId": "welcome-1",
                "role": "assistant",
                "text": "Welcome to Apollo clinic!!",
                "timestamp": 1772702480279
            }
        ],
        "phone": {
            "provider": {
                "name": ""
            }
        },
        "customer": {
            "number": "web-Ramesh Naik"
        },
        "analysis": {},
        "error": {
            "type": "stt_error",
            "code": "audio_quality_insufficient",
            "message": "Audio quality too low for accurate transcription",
            "provider": "deepgram",
            "retryable": false,
            "details": {
                "confidence_score": 0.12,
                "noise_level": "high",
                "signal_to_noise_ratio": -5,
                "suggested_action": "request_user_repeat"
            }
        }
    }
}