> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truedy.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Post-call Analysis

> Turn call & campaign webhook events into analytics safely

# Post-call Analysis

Webhook receivers are usually where your “real” analytics starts: you want to transform events into durable facts (DB rows) and then aggregate them into dashboards.

This guide shows a practical approach for the events Truedy emits for calls, batch campaigns, and voice training.

## What you can (and can’t) get from webhooks

### You can reliably get from call events (`call.ended`, `call.failed`, `call.billed`)

Webhook payloads include call-level analytics fields such as:

* call duration (e.g. `duration` / `ended` timestamps when present)
* cost and billing fields (e.g. `costUsd`, `billingStatus`, billed duration)
* summary fields (e.g. `summary`, `shortSummary`)
* outcome / termination reason fields (e.g. `endReason`)
* sentiment/context fields when present

These fields are produced by backend parsing of the Ultravox event payload.

### You should not expect full transcript text in the webhook

Truedy stores full transcript content separately after the call ends (via backend fetching + persistence). Webhook payloads are intended for **event-driven state changes and analytics**, not for transporting every transcript token.

If you need the full transcript, use the Truedy API endpoints:

* [GET calls transcript](/api-reference/calls)
* [GET calls recording](/api-reference/calls)

## 1) Persist the raw event (always)

Before transforming anything, store the incoming webhook payload so you can re-run analytics without re-contacting upstream services.

A minimal table design:

* `received_at` (server timestamp)
* `event_type` (top-level `event`)
* `dedupe_key` (your idempotency key)
* `payload` (raw JSON body you received)

This enables:

* replaying historical data
* debugging field differences (camelCase vs snake\_case, missing keys)
* rebuilding aggregates

## 2) Extract a stable dedupe key

Use the rules from:

* [Available Webhooks (event catalogue)](/guides/available-webhooks)
* [Webhook Error Handling & Retries](/guides/webhook-error-handling-and-retries)

In practice:

* for call events, use `data.call.callId` (or equivalent fallback)
* for batch events, use `data.batch_id` (or equivalent fallback)
* for voice events, use `data.voice_id` (or equivalent fallback)

## 3) Transform events into analytics facts

### A. `call.ended`: create a “call\_metrics” fact row

When `event_type == "call.ended"`:

1. Extract:
   * `uv_call_id` (from `data.call.callId` or equivalent)
   * `duration_seconds` (when present)
   * `cost_usd` (from `costUsd` / `cost_usd`-like fields)
   * `summary` / `shortSummary`
   * `endReason`, `billingStatus`
   * `sentiment` (if present)
2. Determine outcome buckets:
   * completed vs failed/no-answer based on `endReason`
3. Write a single row into your analytics table keyed by `(event_type, dedupe_key)` or by `(uv_call_id, event_type)`.

Example “bucket logic” (pseudocode):

```txt theme={null}
if endReason in ["no_answer","unanswered","busy","unreachable"]:
  outcome = "no_answer_or_unreachable"
else:
  outcome = "completed"
```

### B. `batch.status.changed` / `batch.completed`: update campaign aggregates

When receiving batch events:

1. Extract `uv_batch_id` from `data.batch_id` / `data.batchId`.
2. Extract the new status from either:
   * `data.data.status`
   * or `data.status`
3. Update your campaign aggregates:
   * status timeline (draft/scheduled/active/completed)
   * completion timestamp

If you need per-contact drilldowns (who succeeded vs failed), you typically need to join against your own state or use Truedy’s batch contacts endpoints inside your analytics pipeline.

### C. `voice.training.completed` / `voice.training.failed`: update voice readiness

When `voice.training.completed` arrives:

* mark the voice as “ready/active” in your internal systems

When `voice.training.failed` arrives:

* mark the voice as “failed”
* store `error_message` for user display and troubleshooting

## 4) Build dashboards from facts

Once you have durable fact rows (calls metrics, campaign status timeline, voice readiness), your dashboards become simple aggregations:

* calls per hour / per day
* completion rate and failure buckets
* avg duration and cost per call
* sentiment distribution (if present)
* campaign progress over time

Because you stored raw payloads, you can safely evolve extraction logic without losing the original data.

## 5) Optional: enrich with transcripts/recordings

To enrich with full transcript and recordings:

1. You must have the Truedy `call_id` that matches the stored call.
2. Call:
   * `GET /api/public/v1/calls/{call_id}/transcript`
   * `GET /api/public/v1/calls/{call_id}/recording`

How you obtain `call_id` is up to your integration:

* if you initiate calls from your system and persist `call_id`, use that
* if your workflow only has webhook payload identifiers, you may need an additional correlation step in your architecture

## Next steps

* [Available Webhooks (event catalogue)](/guides/available-webhooks)
* [Webhook Error Handling & Retries](/guides/webhook-error-handling-and-retries)
* [Securing Webhooks](/guides/securing-webhooks)
