Thread Events
A thread.completed event represents a finished SMS or WhatsApp conversation.
Messages tell you what happened during the conversation.
The thread tells you how the conversation ended.
It can contain:
- the transcript
- summary
- intent
- outcome
- sentiment
- evaluation results
- follow-ups
- cost information
When it fires
A thread is not completed when the last message arrives.
Voicetta waits for the conversation to become inactive before grading it.
The default inactivity period is 30 minutes.
If a recovery follow-up is needed, the conversation can stay open longer.
Expect thread.completed roughly 30 minutes after the conversation goes quiet, unless a follow-up extends it.
If you need the live conversation, use message events.
Payload
{
"event": "thread.completed",
"event_id": "c82fa013-5b7d-4029-8e4f-8d3c6b5f9a42",
"version": 1,
"occurred_at": "2026-08-20T14:45:00+00:00",
"thread": {
"thread_id": "c82fa013-5b7d-4029-8e4f-8d3c6b5f9a42",
"workspace_id": "2a1f8e7d-4c3b-4a29-9f18-6d5e4c3b2a19",
"assistant_id": "7c6b5a49-3e2d-4c1b-8a97-5f4e3d2c1b09",
"assistant_name": "Front Desk",
"channel": "whatsapp",
"close_reason": "graded",
"message_count": 6,
"started_at": "2026-08-20T14:12:00+00:00",
"last_message_at": "2026-08-20T14:19:00+00:00",
"completed_at": "2026-08-20T14:45:00+00:00",
"anchor_message_id": "b71e9d02-4a6c-4f18-9d3e-7c2b5a4e8f31",
"customer": {
"customer_id": "5d4c3b2a-1f09-4e8d-7c6b-5a493e2d1c0b",
"name": "Jane Texter",
"phone": "+15551234567"
},
"transcript": [
{
"role": "user",
"content": "Do you have a room free on Friday?"
},
{
"role": "assistant",
"content": "Yes, we have a double available."
}
],
"analysis": {
"summary": "Customer asked about Friday availability and held a double room.",
"intent": "booking_enquiry",
"outcome": "booked",
"sentiment": "positive"
},
"evaluations": [
{
"evaluation_id": "3e2d1c0b-5a49-4f8e-9d7c-6b5a493e2d1c",
"title": "Offered a booking reference",
"result": "pass",
"initial_result": "fail",
"rationale": "The reference was given after the follow-up.",
"recovered": true
}
],
"costs": {
"currency": "USD",
"total": "0.0300",
"charged_message_count": 3
},
"follow_ups": [
{
"status": "sent",
"channel": "whatsapp",
"sent_at": "2026-08-20T14:40:00+00:00"
}
]
}
}Reading the fields
thread_idis the conversation id.thread_idis also present on message events.anchor_message_ididentifies the message associated with the grading.completed_atis when grading finished.last_message_atis when the conversation went quiet.message_countis the number of messages in the transcript.evaluations[].recoveredtells you whether a failed criterion was fixed by a follow-up.
Costs
costs.total is an indicative value based on message usage.
It is not the amount charged on your invoice.
Use your invoice for billing.
close_reason
Always read close_reason.
Not every completed thread has evaluation results.
| Reason | Meaning | Graded? |
|---|---|---|
graded | Normal completed conversation | Yes |
no_reply_after_follow_up | Recovery follow-up was not answered | Yes |
customer_opted_out | Customer opted out | Partly |
no_enabled_evaluations | No evaluations were enabled | No |
empty_transcript | Nothing could be graded | No |
grading_failed | Grading failed | No |
follow_up_failed | Recovery follow-up failed | No |
follow_up_daily_cap | Daily follow-up limit was reached | No |
follow_up_no_criteria | Nothing needed recovery | Partly |
follow_up_no_customer_phone | No phone number was available | No |
follow_up_missing_openai_key | Follow-up generation was not configured | No |
follow_up_send_ambiguous | Follow-up delivery could not be confirmed | No |
follow_up_skipped:<reason> | Follow-up was skipped | No |
Do not treat every thread as graded.
For quality reporting, use graded and no_reply_after_follow_up as your graded population.
Treat the other close reasons as not graded.
Why ungraded threads are still delivered
A conversation happened even if grading failed.
We still send it.
That gives your product a complete picture of communication instead of silently hiding conversations because evaluation did not run.
What threads do not contain
Threads do not contain:
- recordings
- voice latency
- voice configuration
is_test
Those fields only apply to voice calls.
Reading threads through the API
GET /v1/threadsYou can filter by date, assistant, channel, and close_reason.
Use:
close_reason=gradedwhen you only want conversations that produced evaluation results.