# Conversation lifecycle

This page explains how conversations move through lifecycle states, how timeouts work, and when to close conversations.

## Lifecycle states

The following table describes the three possible lifecycle states of a conversation.

| State      | Description                                                                                                                                                                                                                                         |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ACTIVE`   | An active conversation that's receiving new communications. This is the default state when a conversation is created.                                                                                                                               |
| `INACTIVE` | Temporarily paused. The conversation reopens if new traffic arrives from a participant. This state is optional: you can turn it on in your [conversation configuration](/docs/conversations/orchestrator/concepts/core#conversation-configuration). |
| `CLOSED`   | Finalized and immutable. No new communications can be added. Any new matching traffic starts a new conversation.                                                                                                                                    |

### State transitions

Conversations transition between states based on activity and configured timeouts:

```mermaid
---
config:
  theme: base
  themeVariables:
    transitionLabelColor: '#ffffff'
---
stateDiagram-v2
    direction LR
    [*] --> Active: Created
    Active --> Inactive: Inactive timeout
    Inactive --> Active: New traffic
    Inactive --> Closed: Closed timeout
    Active --> Closed: Closed timeout or closed by you
    Closed --> [*]
```

Conversations move from `ACTIVE` to `INACTIVE` when the inactive timeout expires, and from `INACTIVE` to `CLOSED` when the closed timeout expires.

An `INACTIVE` conversation returns to `ACTIVE` when new traffic arrives from any participant—inbound or outbound. Traffic from a different customer starts a new conversation instead. Once `CLOSED`, a conversation can't be reopened: any matching traffic starts a new conversation.

You can also close conversations directly, bypassing the `INACTIVE` state.

## Timeouts

Timeouts control when Conversation Orchestrator moves a conversation between states.

* The **inactive timeout** is the duration from the last communication to the `INACTIVE` state.
* The **closed timeout** is the duration from the last communication to the `CLOSED` state.

Both timers are measured from the last communication, not relative to entering the inactive state. Timeout values are in minutes. With `{"inactive": 10, "closed": 30}`, a conversation moves to inactive 10 minutes after the last communication and to closed at the 30 minute mark.

All conversations have a maximum lifespan of three years from creation, regardless of activity.

### Voice timeouts behave differently

Voice conversations measure time from transcription events, not call events. Conversation Orchestrator doesn't receive call start or end events—it only receives transcribed utterances as communications. If a call has no speech, no communications are created, the timeout clock never starts, and the conversation stays active indefinitely.

This matters most for pre-created conversations (for example, a conversation created by Twilio Agent Connect or your own API call) where the customer never speaks. To avoid orphaned active conversations, use one of the following patterns:

* **Close immediately when the call ends** (recommended): set `"statusTimeouts": null` on the voice channel. The conversation closes when the transcription stream ends.
* **Allow a callback window**: set short timeouts, for example `{"inactive": 5, "closed": 30}`. A follow-up call within the window reopens the conversation and resets the timers.

> \[!WARNING]
>
> If a voice call produces zero transcription (silent call, immediate hangup, or transcription fails to start), the conversation remains `ACTIVE` indefinitely because no communication was recorded and the timeout clock never starts. Monitor for stuck conversations and close them with the API. See [Close conversations with the API](#close-conversations-with-the-api).

### Multi-channel timeout behavior

When a conversation spans multiple channels (for example, voice and SMS with `GROUP_BY_PROFILE`):

* Each channel's timeout applies independently to communications on that channel.
* The conversation doesn't close until all active channels' timeouts have expired.
* A new communication on any channel resets that channel's clock.

For example, a customer is on a voice call (`statusTimeouts: null`) and sends an SMS during the call (SMS `statusTimeouts: {"inactive": 10, "closed": 60}`). When the voice call ends, the voice channel's final transcription event closes out voice, but the SMS channel's timeout hasn't expired. The conversation remains `ACTIVE` until 10 minutes after the last SMS, then transitions to `INACTIVE`, then to `CLOSED` 60 minutes after the last SMS.

### TTS fragmentation and voice communications

When you use Conversation Relay, each TTS fragment is written as a separate communication, not one communication per complete agent response. A single agent utterance might produce three to five communications depending on how the TTS engine chunks the text.

This behavior has three effects:

* Communication lists are longer than expected.
* Intelligence operators with the `COMMUNICATION` trigger fire per-fragment (cost multiplier).
* Webhook handlers (`statusCallbacks`) receive more events than anticipated.

This is expected behavior. If you need per-turn analysis rather than per-fragment, use the `CONVERSATION_END` trigger on intelligence operators instead of `COMMUNICATION`.

### Configure timeouts

Configure timeouts when you create a conversation configuration or update them later for an existing configuration. You can set different timeout values for each channel (SMS, Voice).

## Console

To configure timeouts using the Twilio Console, follow these steps:

1. Sign in to the [Twilio Console](https://1console.twilio.com).
2. Go to **Products & services** > **Conversation Orchestrator** > **Conversation Configurations**.
3. Click the name of the conversation configuration you want to update.
4. In the **Conversation lifecycle and timeouts** section, click **Edit**.
5. For each channel you want to configure, select a timeout option.
6. Click **Save**.

## API

To configure timeouts programmatically, send a `PUT` request to the Configurations endpoint with a `channelSettings` object containing per-channel `statusTimeouts`. See the [Conversation Orchestrator API reference](/docs/api/conversations/v2).

Configure timeouts

```js
// Download the helper library from https://www.twilio.com/docs/node/install
const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";

// Find your Account SID at twilio.com/console
// Provision API Keys at twilio.com/console/runtime/api-keys
// and set the environment variables. See http://twil.io/secure
// For local testing, you can use your Account SID and Auth token
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const apiKey = process.env.TWILIO_API_KEY;
const apiSecret = process.env.TWILIO_API_SECRET;
const client = twilio(apiKey, apiSecret, { accountSid: accountSid });

async function updateConfiguration() {
  const configuration = await client.conversations.v2
    .configurations("CONVERSATION_CONFIGURATION_ID")
    .update({
      description: "Conversation configuration description",
      conversationGroupingType: "GROUP_BY_PROFILE",
      memoryStoreId: "memory_store_xxxxxxxxxxxxxxxxxx",
      channelSettings: {
        SMS: {
          statusTimeouts: {
            inactive: 10,
            closed: 30,
          },
        },
        VOICE: {
          statusTimeouts: {
            inactive: 5,
            closed: 15,
          },
        },
      },
    });

  console.log(configuration.statusUrl);
}

updateConfiguration();
```

```python
# Download the helper library from https://www.twilio.com/docs/python/install
import os
from twilio.rest import Client
from twilio.rest.conversations.v2 import ConfigurationList

# Find your Account SID at twilio.com/console
# Provision API Keys at twilio.com/console/runtime/api-keys
# and set the environment variables. See http://twil.io/secure
# For local testing, you can use your Account SID and Auth token
api_key = os.environ["TWILIO_API_KEY"]
api_secret = os.environ["TWILIO_API_SECRET"]
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
client = Client(api_key, api_secret, account_sid)

configuration = client.conversations.v2.configurations(
    "CONVERSATION_CONFIGURATION_ID"
).update(
    update_configuration_request=ConfigurationList.UpdateConfigurationRequest(
        {
            "description": "Conversation configuration description",
            "conversationGroupingType": "GROUP_BY_PROFILE",
            "memoryStoreId": "memory_store_xxxxxxxxxxxxxxxxxx",
            "channelSettings": {
                "SMS": ConfigurationList.UpdateConfigurationRequestChannelSettingsValue(
                    {
                        "statusTimeouts": ConfigurationList.UpdateConfigurationRequestChannelSettingsValueStatusTimeouts(
                            {"inactive": 10, "closed": 30}
                        )
                    }
                ),
                "VOICE": ConfigurationList.UpdateConfigurationRequestChannelSettingsValue(
                    {
                        "statusTimeouts": ConfigurationList.UpdateConfigurationRequestChannelSettingsValueStatusTimeouts(
                            {"inactive": 5, "closed": 15}
                        )
                    }
                ),
            },
        }
    )
)

print(configuration.status_url)
```

```csharp
// Install the C# / .NET helper library from twilio.com/docs/csharp/install

using System;
using Twilio;
using Twilio.Rest.Conversations.V2;
using System.Threading.Tasks;
using System.Collections.Generic;

class Program {
    public static async Task Main(string[] args) {
        // Find your Account SID at twilio.com/console
        // Provision API Keys at twilio.com/console/runtime/api-keys
        // and set the environment variables. See http://twil.io/secure
        // For local testing, you can use your Account SID and Auth token
        string apiKey = Environment.GetEnvironmentVariable("TWILIO_API_KEY");
        string apiSecret = Environment.GetEnvironmentVariable("TWILIO_API_SECRET");
        string accountSid = Environment.GetEnvironmentVariable("TWILIO_ACCOUNT_SID");

        TwilioClient.Init(apiKey, apiSecret, accountSid);

        var configuration = await ConfigurationResource.UpdateAsync(new UpdateConfigurationOptions(
            "CONVERSATION_CONFIGURATION_ID") {
            UpdateConfigurationRequest =
                new ConfigurationResource.UpdateConfigurationRequest.Builder()
                    .WithDescription("Conversation configuration description")
                    .WithConversationGroupingType("GROUP_BY_PROFILE")
                    .WithMemoryStoreId("memory_store_xxxxxxxxxxxxxxxxxx")
                    .WithChannelSettings(new Dictionary<
                                         string,
                                         ConfigurationResource
                                             .UpdateConfigurationRequestChannelSettingsValue>() {
                        { "SMS",
                          new ConfigurationResource.UpdateConfigurationRequestChannelSettingsValue
                              .Builder()
                              .WithStatusTimeouts(
                                  new ConfigurationResource
                                      .UpdateConfigurationRequestChannelSettingsValueStatusTimeouts
                                      .Builder()
                                      .WithInactive(10)
                                      .WithClosed(30)
                                      .Build())
                              .Build() },
                        { "VOICE",
                          new ConfigurationResource.UpdateConfigurationRequestChannelSettingsValue
                              .Builder()
                              .WithStatusTimeouts(
                                  new ConfigurationResource
                                      .UpdateConfigurationRequestChannelSettingsValueStatusTimeouts
                                      .Builder()
                                      .WithInactive(5)
                                      .WithClosed(15)
                                      .Build())
                              .Build() }
                    })
                    .Build()
        });

        Console.WriteLine(configuration.StatusUrl);
    }
}
```

```java
// Install the Java helper library from twilio.com/docs/java/install

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.twilio.Twilio;
import com.twilio.rest.conversations.v2.Configuration;

public class Example {
    // Find your Account SID at twilio.com/console
    // Provision API Keys at twilio.com/console/runtime/api-keys
    // and set the environment variables. See http://twil.io/secure
    // For local testing, you can use your Account SID and Auth token
    public static final String API_KEY = System.getenv("TWILIO_API_KEY");
    public static final String API_SECRET = System.getenv("TWILIO_API_SECRET");
    public static final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");

    public static void main(String[] args) {
        Twilio.init(API_KEY, API_SECRET, ACCOUNT_SID);

        Configuration.UpdateConfigurationRequest updateConfigurationRequest =
            Configuration.UpdateConfigurationRequest
                .builder("Conversation configuration description",
                    Configuration.ConversationGroupingType.GROUP_BY_PROFILE,
                    "memory_store_xxxxxxxxxxxxxxxxxx",
                    new HashMap<String, Map<String, Object>>() {
                        {
                            put("SMS", new HashMap<String, Map<String, Object>>() {
                                {
                                    put("statusTimeouts", new HashMap<String, Object>() {
                                        {
                                            put("inactive", 10);
                                            put("closed", 30);
                                        }
                                    });
                                }
                            });
                            put("VOICE", new HashMap<String, Map<String, Object>>() {
                                {
                                    put("statusTimeouts", new HashMap<String, Object>() {
                                        {
                                            put("inactive", 5);
                                            put("closed", 15);
                                        }
                                    });
                                }
                            });
                        }
                    })
                .build();

        Configuration.UpdateConfigurationResponse response =
            Configuration.updater("CONVERSATION_CONFIGURATION_ID")
                .setUpdateConfigurationRequest(updateConfigurationRequest)
                .update();

        System.out.println(response.getStatusUrl());
    }
}
```

```ruby
# Download the helper library from https://www.twilio.com/docs/ruby/install
require 'twilio-ruby'

# Find your Account SID at twilio.com/console
# Provision API Keys at twilio.com/console/runtime/api-keys
# and set the environment variables. See http://twil.io/secure
# For local testing, you can use your Account SID and Auth token
api_key = ENV['TWILIO_API_KEY']
api_secret = ENV['TWILIO_API_SECRET']
account_sid = ENV['TWILIO_ACCOUNT_SID']
@client = Twilio::REST::Client.new(api_key, api_secret, account_sid)

configuration = @client
                .conversations
                .v2
                .configurations('CONVERSATION_CONFIGURATION_ID')
                .update(
                  update_configuration_request: {
                    'description' => 'Conversation configuration description',
                    'conversationGroupingType' => 'GROUP_BY_PROFILE',
                    'memoryStoreId' => 'memory_store_xxxxxxxxxxxxxxxxxx',
                    'channelSettings' => {
                      'SMS' => {
                        'statusTimeouts' => {
                          'inactive' => 10,
                          'closed' => 30
                        }
                      },
                      'VOICE' => {
                        'statusTimeouts' => {
                          'inactive' => 5,
                          'closed' => 15
                        }
                      }
                    }
                  }
                )

puts configuration.status_url
```

```bash
UPDATE_CONFIGURATION_REQUEST_OBJ=$(cat << EOF
{
  "description": "Conversation configuration description",
  "conversationGroupingType": "GROUP_BY_PROFILE",
  "memoryStoreId": "memory_store_xxxxxxxxxxxxxxxxxx",
  "channelSettings": {
    "SMS": {
      "statusTimeouts": {
        "inactive": 10,
        "closed": 30
      }
    },
    "VOICE": {
      "statusTimeouts": {
        "inactive": 5,
        "closed": 15
      }
    }
  }
}
EOF
)
curl -X PUT "https://conversations.twilio.com/v2/ControlPlane/Configurations/CONVERSATION_CONFIGURATION_ID" \
--json "$UPDATE_CONFIGURATION_REQUEST_OBJ" \
-u $TWILIO_API_KEY:$TWILIO_API_SECRET
```

Replace *CONVERSATION\_CONFIGURATION\_ID* with your conversation configuration ID.

## Close conversations with the API

You can close conversations programmatically by sending a `PUT` request to the Conversations endpoint with `"status": "CLOSED"`. See the [Conversation Orchestrator API reference](/docs/api/conversations/v2).

Close a conversation

```js
// Download the helper library from https://www.twilio.com/docs/node/install
const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";

// Find your Account SID at twilio.com/console
// Provision API Keys at twilio.com/console/runtime/api-keys
// and set the environment variables. See http://twil.io/secure
// For local testing, you can use your Account SID and Auth token
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const apiKey = process.env.TWILIO_API_KEY;
const apiSecret = process.env.TWILIO_API_SECRET;
const client = twilio(apiKey, apiSecret, { accountSid: accountSid });

async function updateConversationById() {
  const conversation = await client.conversations.v2
    .conversations("CONVERSATION_ID")
    .update({
      status: "CLOSED",
    });

  console.log(conversation.id);
}

updateConversationById();
```

```python
# Download the helper library from https://www.twilio.com/docs/python/install
import os
from twilio.rest import Client
from twilio.rest.conversations.v2 import ConversationList

# Find your Account SID at twilio.com/console
# Provision API Keys at twilio.com/console/runtime/api-keys
# and set the environment variables. See http://twil.io/secure
# For local testing, you can use your Account SID and Auth token
api_key = os.environ["TWILIO_API_KEY"]
api_secret = os.environ["TWILIO_API_SECRET"]
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
client = Client(api_key, api_secret, account_sid)

conversation_by_id = client.conversations.v2.conversations(
    "CONVERSATION_ID"
).update(
    update_conversation_by_id_request=ConversationList.UpdateConversationByIdRequest(
        {"status": "CLOSED"}
    )
)

print(conversation_by_id.id)
```

```csharp
// Install the C# / .NET helper library from twilio.com/docs/csharp/install

using System;
using Twilio;
using Twilio.Rest.Conversations.V2;
using System.Threading.Tasks;
using System.Collections.Generic;

class Program {
    public static async Task Main(string[] args) {
        // Find your Account SID at twilio.com/console
        // Provision API Keys at twilio.com/console/runtime/api-keys
        // and set the environment variables. See http://twil.io/secure
        // For local testing, you can use your Account SID and Auth token
        string apiKey = Environment.GetEnvironmentVariable("TWILIO_API_KEY");
        string apiSecret = Environment.GetEnvironmentVariable("TWILIO_API_SECRET");
        string accountSid = Environment.GetEnvironmentVariable("TWILIO_ACCOUNT_SID");

        TwilioClient.Init(apiKey, apiSecret, accountSid);

        var conversation = await ConversationResource.UpdateAsync(
            new UpdateConversationOptions("CONVERSATION_ID") {
                UpdateConversationByIdRequest =
                    new ConversationResource.UpdateConversationByIdRequest.Builder()
                        .WithStatus("CLOSED")
                        .Build()
            });

        Console.WriteLine(conversation.Id);
    }
}
```

```java
// Install the Java helper library from twilio.com/docs/java/install

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.twilio.Twilio;
import com.twilio.rest.conversations.v2.Conversation;

public class Example {
    // Find your Account SID at twilio.com/console
    // Provision API Keys at twilio.com/console/runtime/api-keys
    // and set the environment variables. See http://twil.io/secure
    // For local testing, you can use your Account SID and Auth token
    public static final String API_KEY = System.getenv("TWILIO_API_KEY");
    public static final String API_SECRET = System.getenv("TWILIO_API_SECRET");
    public static final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");

    public static void main(String[] args) {
        Twilio.init(API_KEY, API_SECRET, ACCOUNT_SID);

        Conversation.UpdateConversationByIdRequest updateConversationByIdRequest =
            Conversation.UpdateConversationByIdRequest.builder(Conversation.Status.CLOSED).build();

        Conversation.UpdateConversationResponse response =
            Conversation.updater("CONVERSATION_ID")
                .setUpdateConversationByIdRequest(updateConversationByIdRequest)
                .update();

        System.out.println(response.getId());
    }
}
```

```ruby
# Download the helper library from https://www.twilio.com/docs/ruby/install
require 'twilio-ruby'

# Find your Account SID at twilio.com/console
# Provision API Keys at twilio.com/console/runtime/api-keys
# and set the environment variables. See http://twil.io/secure
# For local testing, you can use your Account SID and Auth token
api_key = ENV['TWILIO_API_KEY']
api_secret = ENV['TWILIO_API_SECRET']
account_sid = ENV['TWILIO_ACCOUNT_SID']
@client = Twilio::REST::Client.new(api_key, api_secret, account_sid)

conversation = @client
               .conversations
               .v2
               .conversations('CONVERSATION_ID')
               .update(
                 update_conversation_by_id_request: {
                   'status' => 'CLOSED'
                 }
               )

puts conversation.id
```

```bash
UPDATE_CONVERSATION_BY_ID_REQUEST_OBJ=$(cat << EOF
{
  "status": "CLOSED"
}
EOF
)
curl -X PUT "https://conversations.twilio.com/v2/Conversations/CONVERSATION_ID" \
--json "$UPDATE_CONVERSATION_BY_ID_REQUEST_OBJ" \
-u $TWILIO_API_KEY:$TWILIO_API_SECRET
```

Replace *CONVERSATION\_ID* with the conversation ID.

## Next steps

* [Core concepts](/docs/conversations/orchestrator/concepts/core): Learn about conversation configurations, conversations, communications, and participants.
* [Ingestion modes](/docs/conversations/orchestrator/concepts/ingestion): Understand how messaging and voice traffic becomes part of a conversation.
* [Troubleshooting](/docs/conversations/orchestrator/troubleshooting): Resolve conversations stuck in `ACTIVE` and other lifecycle issues.
* [Quickstart](/docs/conversations/orchestrator/quickstart): Create your first conversation configuration.
