List endpoints return a trimmed projection:
id, status, conversation_duration, created_at and a
short telephony_data. Fetch the execution by id, or receive it on a webhook, to get everything below.
A payload can also contain keys that are not listed here; those are internal and carry no compatibility
guarantee, so do not build on them.Before you build on this
Three things account for most of the bugs people hit reading this payload.Nothing is final until the status is terminal
conversation_duration, total_cost, telephony_data.recording_url, transcript and extracted_data
are null, 0 or empty until the call finishes and post-processing runs. Read them earlier and you will
record zeros. See the status lifecycle.
status: "completed" does not mean a conversation happened
status is derived from the telephony provider’s call-lifecycle field, not from the hangup cause, so a
call the callee rejected can still arrive as completed. Check conversation_duration > 0 before
treating a call as answered. telephony_data.hangup_reason and hangup_provider_code carry the real
ending.
A webhook fires several times for one call
Bolna POSTs this payload as the call’s status changes, so a single call produces multiple deliveries carrying the sameid. Make your handler idempotent and key it on id, or you will count the same call
more than once.
Together those three give the shape of a correct handler: ignore non-terminal deliveries, then decide
what actually happened before recording anything.
Handling a delivery
handle is keyed on execution["id"], so a repeated delivery for the same call overwrites rather than
duplicates. That is what makes it safe against point 3.A complete payload
A real completed call. The agent’s welcome message played, the caller never spoke, and the agent hung up after its silence timeout.Completed execution
Identity and linkage
Status and lifecycle
Timing
created_at and initiated_at differ by the queue wait. Use initiated_at when measuring
dial-to-answer behaviour, and created_at when reconciling against the moment you made the request.
Scheduled calls also use the statuses scheduled and rescheduled, which sit before queued.Conversation content
Telephony
All undertelephony_data.
Cost
total_cost and cost_breakdown are the billing figures, reported in cents. The dashboard presents
the same numbers as credits. See Call pricing.
Usage
usage_breakdown is the unit-level detail behind the cost. Read it when you want to know why a call
cost what it did, or which models actually ran.
Latency
latency_data is the diagnostic record. See Call latencies for how to read it.
Transfers, context and retries
Reading the payload well
Did a real conversation happen?
Did a real conversation happen?
Check
conversation_duration > 0, then look for a line starting with user: in transcript.
status alone is not enough, because it comes from the telephony provider’s lifecycle field and can
read completed for a call the callee rejected. telephony_data.hangup_reason and
hangup_provider_code tell you how it really ended.Why did this call cost what it did?
Why did this call cost what it did?
Start at
cost_breakdown.llm_breakdown, because extractions and summarisation are separate LLM passes
on top of the conversation, so an agent with many dispositions costs more per call. Then check
usage_breakdown.provider_source: components billing to bolna are charged to your wallet, while
ones billing to your own connected key are not.Why was the agent slow?
Why was the agent slow?
latency_data.time_to_first_audio is what the caller felt. Split it with the per-component
time_to_connect and turns values to see which leg was slow, then compare
usage_breakdown.endpointing and incremental_delay against a call that felt fine. See
Call latencies.Why did the agent talk over the caller?
Why did the agent talk over the caller?
latency_data.interruption_stats, specifically agent_interrupted_user_count,
longest_agent_monologue_ms and barge_in_recovery_rate. The settings that change this behaviour,
interruption thresholds and endpointing, live in the Engine tab.Can I get this pushed to me instead of polling?
Can I get this pushed to me instead of polling?
Yes. Set
webhook_url on the agent and Bolna POSTs this same payload as the call progresses. Whitelist
13.203.39.153, 13.126.9.249 and 13.202.133.53. See
Webhooks.
