Skip to main content
The end-of-call-report event is triggered when a call completes and contains comprehensive analytics, recordings, cost breakdown, and AI-powered analysis results.
This is the most data-rich webhook event, containing full call metrics, analysis results, and recording information. It’s essential for post-call processing and analytics.

When It’s Triggered

This event is sent once per call when:
  • The call ends naturally (user or assistant hangs up)
  • The call is terminated due to timeout
  • The call is disconnected due to technical issues
  • Call forwarding completes

Event Structure

{
    "message": {
        "timestamp": 1772702523935,
        "type": "end-of-call-report",
        "call": { /* Enhanced Call Object with metrics */ },
        "assistant": { /* Assistant Object */ },
        "messages": [ /* Complete conversation history */ ],
        "phone": { /* Phone Object */ },
        "customer": { /* Customer Object */ },
        "analysis": { /* Complete Analysis Results */ }
    }
}

Key Features

Enhanced Call Object

The call object includes additional fields only available at call completion:
FieldTypeDescription
endAtstringISO timestamp when call ended
costobjectDetailed cost breakdown by service
metricsobjectComprehensive call performance metrics
recordingobjectRecording file location and metadata

Complete Analysis Results

Analysis Object

The analysis object contains AI-powered analysis results for the call.
Analysis data is primarily populated in end-of-call-report events. Other events may contain empty analysis objects that will be filled once the call completes.
FieldTypeDescription
summarystringAI-generated summary of the call conversation
successEvaluationstringAssessment of call success: “Excellent”, “Good”, “Fair”, “Poor”
structuredDataobjectExtracted structured data based on configured schema
outcomestringCall outcome classification
guardrailsstringGuardrails evaluation result (or “DISABLED”)

Summary

  • Generated automatically if enabled in assistant configuration
  • Provides 2-3 sentence overview of what happened during the call
  • Uses configurable prompt and LLM model

Success Evaluation

  • Determines if call met objectives based on assistant’s system prompt
  • Uses descriptive scale: Excellent, Good, Fair, Poor
  • Configurable evaluation criteria and rubric

Structured Data

  • Extracts specific data points according to predefined JSON schema
  • Commonly used for capturing:
  • Customer information (name, phone, email)
  • Appointment details
  • Call metadata
  • Custom business data

Example Structured Data Schema

{
    "type": "object",
    "properties": {
        "Name": {
            "type": "string",
            "description": "Name of the patient"
        },
        "phone number": {
            "type": "string",
            "description": "Phone number of patient"
        },
        "appointment_date": {
            "type": "string",
            "description": "Scheduled appointment date"
        }
    },
    "required": [
        "Name"
    ]
}

Cost Breakdown

Detailed cost tracking across all services:
FieldTypeDescription
cost.callDurationnumberTotal call duration in seconds
cost.interactlyCostnumberInteractly platform fee
cost.phoneCostnumberTelephony provider charges
cost.ttsVendorCostnumberText-to-speech generation costs
cost.sttVendorCostnumberSpeech-to-text transcription costs
cost.llmVendorCostnumberLarge language model API costs
cost.totalCostnumberSum of all costs

Performance Metrics

Comprehensive metrics for optimization:
  • Audio Quality: Packet loss, delays, dropped chunks
  • Response Timing: LLM, TTS, STT latency distributions
  • Turn Statistics: User/bot turns, interruptions, barge-ins
  • Error Tracking: Failed requests, retries, timeout counts

Example Payload

{
  "message": {
    "timestamp": 1772702523935,
    "type": "end-of-call-report",
    "call": {
      "id": "WC-82015760-c3bd-427d-a23b-ba9b07e4ab85",
      "status": "finished",
      "startAt": "2026-03-05T09:21:20.063Z",
      "endAt": "2026-03-05T09:22:01.739Z",
      "assistantCallDuration": 41683,
      "callEndTriggerBy": "bot",
      "recording": {
        "s3Bucket": "interactly-qa-us-recordings",
        "path": "team-id/WC-82015760-c3bd-427d-a23b-ba9b07e4ab85.mp3"
      },
      "cost": {
        "callDuration": 41.676,
        "interactlyCost": 0.006946,
        "phoneCost": 0.0165,
        "ttsVendorCost": 0.010620,
        "sttVendorCost": 0.005348,
        "llmVendorCost": 0.0003385,
        "totalCost": 0.0397529
      }
    },
    "analysis": {
      "summary": "User contacted Apollo clinic to book an appointment but ended the conversation abruptly.",
      "successEvaluation": "Poor",
      "structuredData": {
        "Name": "",
        "phone number": "",
        "serial_number": 0
      }
    }
  }
}

Common Use Cases

Call Analytics & Reporting

def process_call_analytics(event_data):
    call = event_data["message"]["call"]
    analysis = event_data["message"]["analysis"]
    metrics = call.get("metrics", {}).get("callStats", {})

    # Store comprehensive call record
    call_record = {
        "call_id": call["id"],
        "duration_seconds": call["assistantCallDuration"] / 1000,
        "end_trigger": call["callEndTriggerBy"],
        "total_cost": call["cost"]["totalCost"],
        "success_rating": analysis["successEvaluation"],
        "summary": analysis["summary"],
        "user_turns": metrics.get("turns", {}).get("user", 0),
        "bot_turns": metrics.get("turns", {}).get("bot", 0),
        "interruptions": metrics.get("turns", {}).get("interrupted", 0),
        "avg_response_time": metrics.get("llmStats", {}).get("responseLatencyAvg", 0),
        "stt_confidence": metrics.get("sttStats", {}).get("confidenceAvg", 0)
    }

    # Store in analytics database
    analytics_db.call_records.insert_one(call_record)

    # Generate insights
    generate_performance_insights(call_record)

Recording Management

def handle_call_recording(event_data):
    call = event_data["message"]["call"]
    recording = call.get("recording", {})

    if recording.get("s3Bucket") and recording.get("path"):
        # Download recording for processing
        recording_url = f"https://{recording['s3Bucket']}.s3.amazonaws.com/{recording['path']}"

        # Store recording metadata
        recording_record = {
            "call_id": call["id"],
            "s3_bucket": recording["s3Bucket"],
            "s3_path": recording["path"],
            "recording_url": recording_url,
            "duration_ms": call["assistantCallDuration"],
            "file_size": estimate_file_size(call["assistantCallDuration"]),
            "created_at": datetime.utcnow()
        }

        db.recordings.insert_one(recording_record)

        # Queue for additional processing
        queue_recording_analysis(call["id"], recording_url)

CRM Integration

def sync_to_crm(event_data):
    call = event_data["message"]["call"]
    analysis = event_data["message"]["analysis"]
    customer = event_data["message"]["customer"]

    structured_data = analysis.get("structuredData", {})

    # Extract customer information
    customer_name = structured_data.get("Name", "")
    customer_phone = structured_data.get("phone number", "")

    if customer_name or customer_phone:
        # Create or update contact in CRM
        crm_contact = {
            "name": customer_name,
            "phone": customer_phone,
            "last_interaction": call["endAt"],
            "call_summary": analysis["summary"],
            "call_success": analysis["successEvaluation"],
            "call_duration": call["assistantCallDuration"],
            "call_recording_url": get_recording_url(call["recording"])
        }

        crm_client.upsert_contact(customer_phone, crm_contact)

        # Create activity record
        crm_client.create_activity({
            "contact_phone": customer_phone,
            "type": "phone_call",
            "subject": f"AI Assistant Call - {analysis['successEvaluation']}",
            "body": analysis["summary"],
            "duration_seconds": call["assistantCallDuration"] / 1000,
            "date": call["endAt"]
        })

Cost Tracking & Optimization

def track_call_costs(event_data):
    call = event_data["message"]["call"]
    cost = call["cost"]
    metrics = call.get("metrics", {}).get("callStats", {})

    # Store cost breakdown
    cost_record = {
        "call_id": call["id"],
        "assistant_id": call["assistantId"],
        "duration_seconds": cost["callDuration"],
        "total_cost": cost["totalCost"],
        "platform_cost": cost["interactlyCost"],
        "phone_cost": cost["phoneCost"],
        "tts_cost": cost["ttsVendorCost"],
        "stt_cost": cost["sttVendorCost"],
        "llm_cost": cost["llmVendorCost"],
        "cost_per_minute": cost["totalCost"] / (cost["callDuration"] / 60),
        "date": call["endAt"][:10]  # Date for aggregation
    }

    db.costs.insert_one(cost_record)

    # Analyze cost efficiency
    tts_usage = metrics.get("ttsStats", {})
    cache_hit_rate = tts_usage.get("cacheRequests", 0) / max(tts_usage.get("totalRequests", 1), 1)

    # Alert on high costs or low cache efficiency
    if cost["totalCost"] > COST_THRESHOLD:
        alert_high_cost(call["id"], cost["totalCost"])

    if cache_hit_rate < 0.5:
        alert_low_cache_efficiency(call["assistantId"], cache_hit_rate)

Performance Monitoring

def monitor_call_performance(event_data):
    call = event_data["message"]["call"]
    metrics = call.get("metrics", {}).get("callStats", {})

    # Check LLM performance
    llm_stats = metrics.get("llmStats", {})
    avg_response_time = llm_stats.get("responseLatencyAvg", 0)

    if avg_response_time > LLM_LATENCY_THRESHOLD:
        alert_performance_issue(
            "slow_llm_response",
            call["assistantId"],
            avg_response_time
        )

    # Check STT quality
    stt_stats = metrics.get("sttStats", {})
    avg_confidence = stt_stats.get("confidenceAvg", 0)

    if avg_confidence < STT_CONFIDENCE_THRESHOLD:
        alert_performance_issue(
            "low_stt_confidence",
            call["assistantId"],
            avg_confidence
        )

    # Check for audio quality issues
    audio_stats = metrics.get("userAudio", {})
    dropped_chunks = audio_stats.get("droppedChunksCount", 0)

    if dropped_chunks > AUDIO_DROP_THRESHOLD:
        alert_performance_issue(
            "audio_quality_degradation",
            call["id"],
            dropped_chunks
        )

Data Retention

Consider implementing data retention policies for end-of-call reports:
def cleanup_old_call_data():
    # Archive calls older than 90 days
    cutoff_date = datetime.utcnow() - timedelta(days=90)

    old_calls = db.call_reports.find({
        "call.endAt": {"$lt": cutoff_date.isoformat()}
    })

    for call_report in old_calls:
        # Move to archive storage
        archive_call_report(call_report)

        # Clean up large fields but keep summary
        db.call_reports.update_one(
            {"_id": call_report["_id"]},
            {
                "$unset": {
                    "messages": "",
                    "call.metrics": ""
                }
            }
        )

End of Call Report Event Example

This is a complete example of an end-of-call-report webhook event payload.
{
    "message": {
        "timestamp": 1772702523935,
        "type": "end-of-call-report",
        "call": {
            "id": "WC-82015760-c3bd-427d-a23b-ba9b07e4ab85",
            "teamId": "67c0231ae6880fe48ef929ee",
            "assistantId": "697769ef5e6d94d5ad83e01e",
            "callType": "web",
            "direction": "inbound",
            "startAt": "2026-03-05T09:21:20.063Z",
            "endAt": "2026-03-05T09:22:01.739Z",
            "userNumber": "web-Ramesh Naik",
            "assistantNumber": "697769ef5e6d94d5ad83e01e",
            "status": "finished",
            "phoneCallStatus": "completed",
            "phoneCallStatusReason": "Call is completed",
            "callEndTriggerBy": "bot",
            "assistantCallDuration": 41683,
            "analysis": {
                "summary": "",
                "successEvaluation": "",
                "guardrails": "DISABLED"
            },
            "recording": {
                "s3Bucket": "interactly-qa-us-recordings",
                "path": "67c0231ae6880fe48ef929ee/WC-82015760-c3bd-427d-a23b-ba9b07e4ab85.mp3"
            },
            "cost": {
                "analysis": {
                    "total": 0
                },
                "callDuration": 41.676,
                "ttsGenCharsTotal": 118,
                "inputTokensTotal": 485,
                "outputTokensTotal": 32,
                "interactlyCost": 0.006946,
                "phoneCost": 0.0165,
                "ttsVendorCost": 0.010620000000000001,
                "sttVendorCost": 0.005348418610800001,
                "llmVendorCost": 0.0003385,
                "totalCost": 0.0397529186108
            },
            "assistantOverrides": {
                "dynamicVariables": {
                    "serial_number": ""
                },
                "variablesValidations": {
                    "serial_number": "none"
                }
            },
            "metadata": {},
            "metrics": {
                "callStats": {
                    "userAudio": {
                        "delayedChunksCount": 70,
                        "latencyAvg": 105.05714285714286,
                        "latencyTotal": 7354,
                        "latencyIntervals": [
                            {
                                "max": 693,
                                "count": 12,
                                "avg": 151.75,
                                "duration": 8189,
                                "cumulativeDuration": 8189
                            },
                            {
                                "max": 113,
                                "count": 30,
                                "avg": 96.16666666666667,
                                "duration": 8000,
                                "cumulativeDuration": 16189
                            },
                            {
                                "max": 111,
                                "count": 25,
                                "avg": 97.6,
                                "duration": 7999,
                                "cumulativeDuration": 24188
                            },
                            {
                                "max": 88,
                                "count": 1,
                                "avg": 88,
                                "duration": 8000,
                                "cumulativeDuration": 32188
                            },
                            {
                                "max": 0,
                                "count": 0,
                                "avg": 0,
                                "duration": 8000,
                                "cumulativeDuration": 40188
                            },
                            {
                                "max": 64,
                                "count": 2,
                                "avg": 60,
                                "duration": 928,
                                "cumulativeDuration": 41116
                            }
                        ],
                        "cumulativeDuration": 41116,
                        "droppedChunksCount": 0
                    },
                    "turns": {
                        "user": 4,
                        "bot": 4,
                        "played": 4,
                        "interrupted": 0,
                        "skipped": 0
                    },
                    "turnsStats": {
                        "userTurns": [
                            {
                                "start": 0,
                                "end": 3861,
                                "messageId": "user-1"
                            },
                            {
                                "start": 5760,
                                "end": 6786,
                                "messageId": "user-2"
                            },
                            {
                                "start": 7110,
                                "end": 11769,
                                "messageId": "user-3"
                            },
                            {
                                "start": 32970,
                                "end": 34289,
                                "messageId": "user-4"
                            }
                        ],
                        "botTurns": [
                            {
                                "start": 223,
                                "end": 2131,
                                "interrupted": false,
                                "playedDuration": 1.908,
                                "audioDuration": 1.442,
                                "userMessageId": "welcome-1",
                                "stopSpeakingReason": null
                            },
                            {
                                "start": 15902,
                                "end": 20080,
                                "interrupted": false,
                                "playedDuration": 4.178,
                                "audioDuration": 3.207,
                                "userMessageId": "user-3",
                                "stopSpeakingReason": null
                            },
                            {
                                "start": 20081,
                                "end": 23159,
                                "interrupted": false,
                                "playedDuration": 3.078,
                                "audioDuration": 2.371,
                                "userMessageId": "user-3",
                                "stopSpeakingReason": null
                            },
                            {
                                "start": 39344,
                                "end": 41110,
                                "interrupted": false,
                                "playedDuration": 1.766,
                                "audioDuration": 1.303,
                                "userMessageId": "user-4",
                                "stopSpeakingReason": null
                            }
                        ],
                        "botResponseTime": [
                            4133,
                            5055
                        ],
                        "bargeInCount": 0,
                        "bargeInTimeAvg": 0
                    },
                    "ttsStats": {
                        "responseTimeDistribution": {
                            "0": 2,
                            "100": 0,
                            "200": 1,
                            "300": 1,
                            "600": 0,
                            "1000": 0,
                            "3000": 0,
                            "5000": 0,
                            "10000": 0
                        },
                        "totalRequests": 4,
                        "latencyAvg": 163.75,
                        "latencyTotal": 655,
                        "errors": 0,
                        "retries": 0,
                        "apiRequests": 2,
                        "apiLatencyTotal": 643,
                        "apiLatencyAvg": 321.5,
                        "cacheRequests": 2,
                        "cacheLatencyTotal": 12,
                        "cacheLatencyAvg": 6
                    },
                    "llmStats": {
                        "responseCount": 1,
                        "responseLatencyTotal": 2557.396173477173,
                        "responseLatencyAvg": 2557.396173477173,
                        "errors": 0,
                        "hangups": 0,
                        "responseTimeDistribution": {
                            "0": 0,
                            "100": 0,
                            "300": 0,
                            "600": 0,
                            "1000": 0,
                            "1500": 0,
                            "2000": 1,
                            "5000": 0,
                            "10000": 0
                        },
                        "inputTokens": 485,
                        "outputTokens": 32,
                        "inputTokensAvg": 485,
                        "outputTokensAvg": 32
                    },
                    "sttStats": {
                        "responseTimeDistribution": {
                            "0": 5,
                            "100": 0,
                            "200": 0,
                            "300": 0,
                            "600": 0,
                            "1000": 0,
                            "3000": 0,
                            "5000": 0,
                            "10000": 0
                        },
                        "responseCount": 5,
                        "responseLatencyAvg": -309.3994399999998,
                        "relativeLatencyAvg": -296.3994399999998,
                        "responseLatencyTotal": -1546.9971999999989,
                        "confidenceAvg": 0.6880248999999999,
                        "connectionRetries": 0,
                        "errors": 0,
                        "confidenceDistribution": {
                            "0": 1,
                            "0.1": 0,
                            "0.3": 0,
                            "0.5": 0,
                            "0.7": 3,
                            "0.9": 0
                        },
                        "eouProbabilityDistribution": {
                            "0": 0,
                            "0.1": 0,
                            "0.3": 0,
                            "0.5": 0,
                            "0.7": 0,
                            "0.9": 0
                        }
                    }
                }
            }
        },
        "assistant": {
            "_id": "697769ef5e6d94d5ad83e01e",
            "name": "Mary Dental - main"
        },
        "messages": [
            {
                "messageId": "welcome-1",
                "role": "assistant",
                "text": "Welcome to Apollo clinic!!",
                "timestamp": 1772702480279
            },
            {
                "messageId": "user-1",
                "role": "user",
                "text": "Hello.",
                "timestamp": 1772702485138
            },
            {
                "messageId": "user-2",
                "role": "user",
                "text": "Can you book an appointment?",
                "timestamp": 1772702488063
            },
            {
                "messageId": "user-3",
                "role": "user",
                "text": "Actually, never mind.",
                "timestamp": 1772702493047
            },
            {
                "messageId": "user-3-response-1772702495604",
                "role": "assistant",
                "text": "Hi there! I'd be happy to help you book an appointment with Dr. Sam.",
                "timestamp": 1772702495958
            },
            {
                "messageId": "user-4",
                "role": "user",
                "text": "Goodbye.",
                "timestamp": 1772702520000
            },
            {
                "messageId": "user-4-response-1772702521000",
                "role": "assistant",
                "text": "Thank you for calling Apollo clinic. Have a great day!",
                "timestamp": 1772702521303
            }
        ],
        "phone": {
            "provider": {
                "name": ""
            }
        },
        "customer": {
            "number": "web-Ramesh Naik"
        },
        "analysis": {
            "summary": "A user contacted Apollo clinic to book an appointment but decided against it and ended the conversation politely.",
            "successEvaluation": "Fair",
            "structuredData": {
                "Name": "",
                "phone number": "",
                "serial_number": 0
            },
            "outcome": "No appointment booked"
        }
    }
}