> ## 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.outcome_set

> A conversation was given an outcome, with the one it replaced.

Fires when a conversation is dispositioned: after a call, when a chat is wrapped
up, or whenever somebody sets the outcome by hand from the conversation screen.

## Payload

```json theme={null}
{
  "id": "whd_5Tq7bN3jH",
  "event": "conversation.outcome_set",
  "createdAt": "2026-09-11T15:02:41.067Z",
  "data": {
    "conversationId": "conv_7Yn2kP",
    "outcome": "meeting_booked",
    "previousOutcome": "no_answer",
    "outcomeReason": "Booked for Tuesday, sending the deck",
    "status": "closed",
    "sourceType": "phone"
  }
}
```

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

<ResponseField name="outcome" type="string">
  The outcome that was set. This is a **disposition key**, not a label.
</ResponseField>

<ResponseField name="previousOutcome" type="string">
  The outcome it replaced. **Omitted** when the conversation had none, which is
  the usual case the first time one is set.
</ResponseField>

<ResponseField name="outcomeReason" type="string">
  The free-text note left alongside the outcome, when there is one.
</ResponseField>

<ResponseField name="status" type="string">
  The conversation's status at the time, for example `open`, `closed`,
  `completed`.
</ResponseField>

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

## Outcomes are per workspace

Each workspace defines its own dispositions under **Settings → Dispositions**,
so `outcome` is whatever key that workspace uses. Do not hard-code a closed set:
switch on the values you care about and let the rest fall through.

Resolve a key to its label with
[`GET /v1/dispositions`](/dev/api-reference/conversations), and cache the map.

<Note>
  **`not_needed` is a real outcome**, seeded into every workspace. It means the
  person on the call decided the conversation warranted no categorisation, which
  is different from nobody having answered the prompt. Exclude it from
  conversion maths rather than treating it as a result.
</Note>

## Worth knowing

* **Setting the same outcome twice still fires.** `previousOutcome` and
  `outcome` can be equal.
* **Correcting an outcome fires again**, with the old value in
  `previousOutcome`. The last delivery for a conversation is the current state,
  but [arrival order is not guaranteed](/dev/webhooks/delivery#order-is-not-guaranteed).
* **Other changes have their own events.** Assigning a conversation delivers
  [`conversation.assigned`](/dev/webhooks/events/conversation-assigned), and
  completing it delivers
  [`conversation.completed`](/dev/webhooks/events/conversation-completed), which
  carries the outcome as it stood at that moment.

## Example

```ts theme={null}
const labels = new Map(
  (await custral.conversations.listDispositions()).map((d) => [d.key, d.name]),
);

custral.on("conversation.outcome_set", async (event) => {
  const {conversationId, outcome, sourceType} = event.data;
  if (outcome === "not_needed") return;

  await analytics.track("call_dispositioned", {
    conversationId,
    channel: sourceType,
    outcome: labels.get(String(outcome)) ?? outcome,
  });
});
```


## Related topics

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