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

# conversation.completed

> A conversation reached the completed status, by hand or when its analysis finished.

Fires when a conversation's status becomes `completed`: somebody marks it done,
or Custral finishes analysing it with no suggested actions left waiting for
review.

## Payload

```json theme={null}
{
  "id": "whd_4Vk9pS2dL",
  "event": "conversation.completed",
  "createdAt": "2026-09-11T15:31:56.207Z",
  "data": {
    "conversationId": "conv_7Yn2kP",
    "status": "completed",
    "sourceType": "phone",
    "outcome": "meeting_booked"
  }
}
```

<ResponseField name="conversationId" type="string" required>
  The conversation. Always present.
</ResponseField>

<ResponseField name="status" type="string">
  Always `completed`.
</ResponseField>

<ResponseField name="sourceType" type="string">
  The channel the conversation came from: `phone`, `email`, `sms`, `widget`,
  `meeting`, `slack`.
</ResponseField>

<ResponseField name="outcome" type="string">
  The conversation's outcome at that moment, as a disposition key. **Omitted**
  when none has been set.
</ResponseField>

## How a conversation completes

* **Mark as Done.** Somebody marks the conversation done from the app, and it
  completes straight away.
* **Analysis finishes.** Custral analyses a conversation after a call ends, after
  its recording is processed, and when it is closed. An analysis that ends with
  no suggested actions waiting completes the conversation.

When analysis leaves suggested actions waiting for review, the conversation's
status is `pending_action` and nothing fires. The event arrives later, when
somebody marks it done or when analysis runs again with nothing left waiting.
An analysis that fails sends nothing either.

## Worth knowing

* **It can fire more than once.** Marking an already completed conversation done
  delivers again, and so does each later analysis run that ends in `completed`,
  for example when insights are regenerated or a report template is added.
  Deduplicate on `conversationId` if you only want the first.
* **`outcome` is a snapshot.** Setting or changing the outcome afterwards
  arrives as
  [`conversation.outcome_set`](/dev/webhooks/events/conversation-outcome-set).
* **Closing sends nothing by itself.** A closed conversation reports
  `completed` only once its analysis finishes.

## Example

```ts theme={null}
custral.on("conversation.completed", async (event) => {
  const {conversationId, sourceType, outcome} = event.data;
  if (sourceType !== "phone") return;

  // Completion can be delivered more than once for one conversation.
  const first = await cache.add(`completed:${conversationId}`); // false if already present
  if (!first) return;

  await reporting.recordCall({conversationId, outcome: outcome ?? null});
});
```


## Related topics

- [Conversations](/dev/webhooks/events/conversations.md)
- [conversation.outcome_set](/dev/webhooks/events/conversation-outcome-set.md)
- [Webhooks](/dev/webhooks/overview.md)
- [Event catalog](/dev/webhooks/events/overview.md)
- [task.completed](/dev/webhooks/events/task-completed.md)
