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

> A conversation was given an owner, with who held it before.

Fires when a conversation's owner is set: routing gives a chat to a teammate,
somebody takes a conversation nobody owned, or a widget chat is transferred.

## Payload

```json theme={null}
{
  "id": "whd_6Kr3mP8wT",
  "event": "conversation.assigned",
  "createdAt": "2026-09-11T15:10:33.582Z",
  "data": {
    "conversationId": "conv_7Yn2kP",
    "assigneeId": "user_5Nc1rT",
    "previousAssigneeId": "user_8Vd4qM",
    "source": "manual",
    "sourceType": "widget"
  }
}
```

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

<ResponseField name="assigneeId" type="string">
  The member who owns the conversation now.
</ResponseField>

<ResponseField name="previousAssigneeId" type="string">
  The member who owned it before. **Omitted** when nobody did, which is the case
  for a first assignment and for every conversation somebody takes.
</ResponseField>

<ResponseField name="source" type="string">
  How the owner was chosen: `routing` when routing rules assigned it, `manual`
  when a person took it or transferred it. Treat any other value as
  informational rather than as an error.
</ResponseField>

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

## Where assignments come from

| What happened                                   | `source`  | `previousAssigneeId`                    |
| ----------------------------------------------- | --------- | --------------------------------------- |
| Routing assigns a widget chat or a Slack thread | `routing` | Omitted, unless it already had an owner |
| Somebody takes a conversation nobody owned      | `manual`  | Omitted                                 |
| A widget chat is transferred to a teammate      | `manual`  | The previous owner                      |

Email, SMS, call and meeting conversations start with no owner, so for those
this fires only when somebody takes one.

## Worth knowing

* **Offering a conversation fires nothing.** When routing offers a conversation
  to several people at once, nobody owns it yet. The event arrives when one of
  them takes it.
* **Taking a conversation that already has an owner changes nothing**, so it
  sends nothing either.
* **The owner can arrive unchanged.** Routing that runs again for a conversation
  can assign the person who already had it, and `previousAssigneeId` then equals
  `assigneeId`.
* **Ids, not names.** `assigneeId` and `previousAssigneeId` are member ids.
  Resolve them against your workspace's member list, and cache the map.

## Example

```ts theme={null}
custral.on("conversation.assigned", async (event) => {
  const {conversationId, assigneeId, previousAssigneeId, sourceType} = event.data;
  if (assigneeId === previousAssigneeId) return;

  await helpdesk.assignTicket({
    externalId: conversationId,
    agent: await directory.lookup(String(assigneeId)),
    channel: sourceType,
  });
});
```


## Related topics

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