Skip to main content

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:

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: 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):
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