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

# Event Types

> Reference for every webhook event OpenCX emits — phone calls, chat sessions, handoffs, CSAT — with payload shapes and delivery guarantees.

`conversation.started`

This event is triggered when a new conversation is started, regardless of the channel.

```typescript theme={"dark"}
{
  event: 'conversation.started';
  organization_id: string;
  data: {
    conversation_id: string;
    assignee:
    | {
      type: 'AI';
      name: string;
    }
    | {
      id: string;
      type: 'human';
      name: string;
      avatar_url: string;
    };
    contact: {
      id?: string;
      name?: string;
      enriched_data?: {
        address?: string | null;
        phone_number?: string;
        email?: string;
      };
      custom_data?: object;
    };
    channel: 'web' | 'email' | 'phone';
    first_message: string;
    timestamp: string;
  };
}
```

***

`conversation.closed`

This event is triggered when a conversation is closed, a conversation can be marked as resolved or not resolved, and can be closed by a human or an AI.

```typescript theme={"dark"}
{
    event: "conversation.closed";
    organization_id: string;
    data: {
        conversation_id?: string;
        timestamp: string;
        contact_id?: string;
        contact?: {
          id: string;
          name?: string;
          email?: string;
          phone_number?: string;
          custom_data?: object;
        };
        human_resolution_status: "resolved" | "not_resolved" | null;
        ai_resolution_status: string | null;
        ai_billing_tier: string | null;
        closed_by:
        | {
            type: "AI";
            name: string;
        }
        | {
            id: number;
            type: "human";
            email: string;
        }
        | {
            type: "workflow";
            id: string;
        }
        | {
            type: "contact";
            id: string;
        };
        is_from_third_party: boolean;
        third_party_name: "dynamics365" | "freshchat" | "freshdesk" | "gorgias" | "hubspot" | "intercom" | "open" | "salesforce" | "twilio_flex" | "zendesk" | "zendesk_v2" | null;
        summary: string | null | undefined;
        contact_reason: string | null | undefined;
        user_sentiment: string | null | undefined;
        language: string | null | undefined;
    }[];
}
```

***

`conversation.handoff_to_human`

This event is triggered when a conversation is handed off to a human agent. Note that a handoff event can happen for multiple reasons; for example, a human agent might decide to take over the conversation, or the AI might decide to hand off the conversation to a human agent.

```typescript theme={"dark"}
{
    event: "conversation.handoff_to_human";
    organization_id: string;
    data: {
        conversation_id: string | null | undefined;
        timestamp: string;
        contact?: {
          id: string;
          name?: string;
          email?: string;
          phone_number?: string;
          custom_data?: object;
        };
        reason:
        | "ai_decided_to_handoff_the_conversation"
        | "ai_is_forbidden_from_handling_this_topic"
        | "human_agent_decided_to_take_over"
        | "human_agent_decided_forced_handoff"
        | "ai_decided_to_auto_handoff_the_issue";
        summary: string;
        contact_reason?: string;
        user_sentiment?: string;
        was_handedoff_to_third_party?: boolean;
        during_work_hours?: boolean;
        third_party_name?: "dynamics365" | "freshchat" | "freshdesk" | "gorgias" | "hubspot" | "intercom" | "open" | "salesforce" | "twilio_flex" | "zendesk" | "zendesk_v2" | null;
        message_count: number;
        language: string;
        integration_metadata?: {
           sunshine?: {
             sunshine_conversation_id: string;
           };
        };
    }[];
}
```

`conversation.contact_response`

When a contact sends a message to your inbox.

```typescript theme={"dark"}
{
  event: 'conversation.contact_response';
  organization_id: string;
  data: {
    conversation_id: string;
    timestamp: string;
    response: string;
    channel: 'web' | 'email' | 'phone_voice' | 'slack' | 'sms' | 'whatsapp' | 'instagram' | 'messenger' | 'api' | 'web_voice';
    custom_data?: Record<string, unknown> | null | undefined;
  }
}
```

`conversation.ai_response`

When an AI responds to a contact message,

```typescript theme={"dark"}
{
  event: 'conversation.ai_response';
  organization_id: string;
  data: {
    conversation_id: string;
    timestamp: string;
    response: string;
    contact: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
    channel: 'web' | 'email' | 'phone_voice' | 'slack' | 'sms' | 'whatsapp' | 'instagram' | 'messenger' | 'api' | 'web_voice';
  }
}
```

***

`conversation.agent_response`

When a human agent responds from the Open inbox,

```typescript theme={"dark"}
{
  event: 'conversation.agent_response';
  organization_id: string;
  data: {
    conversation_id: string;
    timestamp: string;
    customer_id?: string;
    contact?: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
    response: string;
    channel: 'web' | 'email' | 'phone_voice' | 'slack' | 'sms' | 'whatsapp' | 'instagram' | 'messenger' | 'api' | 'web_voice';
  }
}
```

`conversation.third_party_agent_response`

When a third-party software agent responds to a previously handed-off conversation, Open will notify you. In some configurations, Open might also initiate the handoff to a third-party agent, such as Zendesk or Intercom. Even while the third-party agent handles the conversation, Open continues to monitor and send events to your webhooks.

```typescript theme={"dark"}
{
  event: 'conversation.third_party_agent_response';
  organization_id: string;
  data: {
    conversation_id: string | null | undefined;
    timestamp: string;
    contact?: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
    third_party_name: 'dynamics365' | 'freshchat' | 'freshdesk' | 'gorgias' | 'hubspot' | 'intercom' | 'open' | 'salesforce' | 'twilio_flex' | 'zendesk' | 'zendesk_v2';
    response: string;
    message_count: number;
    language: string;
    channel: 'web' | 'email' | 'phone_voice' | 'slack' | 'sms' | 'whatsapp' | 'instagram' | 'messenger' | 'api' | 'web_voice';
    agent: {
      name?: string;
    };
    attachments?: Array<{ mime_type: string; url: string; file_name?: string }>;
  }
}
```

***

`conversation.handoff_to_third_party_failed`

Triggered when a handoff to a third-party platform (e.g. Zendesk, Intercom) fails.

```typescript theme={"dark"}
{
  event: 'conversation.handoff_to_third_party_failed';
  organization_id: string;
  data: {
    conversation_id: string;
    timestamp: string;
    third_party_name: 'dynamics365' | 'freshchat' | 'freshdesk' | 'gorgias' | 'hubspot' | 'intercom' | 'open' | 'salesforce' | 'twilio_flex' | 'zendesk' | 'zendesk_v2';
    error: string;
    channel: 'web' | 'email' | 'phone_voice' | 'slack' | 'sms' | 'whatsapp' | 'instagram' | 'messenger' | 'api' | 'web_voice';
    language: string | null;
    custom_data?: object | null;
    contact?: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
  }
}
```

***

`workflow.run.succeeded`

Triggered when a workflow run completes successfully.

```typescript theme={"dark"}
{
  event: 'workflow.run.succeeded';
  organization_id: string;
  data: {
    run_id: string;
    workflow_id: string;
    trigger_cause: 'automatic' | 'manual' | 'scheduled';
    triggered_by: number | null;
    is_test_run: boolean;
  }
}
```

***

`workflow.run.failed`

Triggered when a workflow run fails.

```typescript theme={"dark"}
{
  event: 'workflow.run.failed';
  organization_id: string;
  data: {
    run_id: string;
    workflow_id: string;
    workflow_name: string;
    session_id: string | null;
    trigger_cause: 'automatic' | 'manual' | 'scheduled';
    triggered_by: number | null;
    is_test_run: boolean;
    error: {
      message: string;
      name: string;
    };
  }
}
```

***

`csat.requested`

Triggered when a CSAT survey is sent to a contact (via email, SMS, WhatsApp, Slack, or shown in the web widget).

```typescript theme={"dark"}
{
  event: 'csat.requested';
  organization_id: string;
  data: {
    session_id: string;
    contact_id: string | null;
    channel: 'web' | 'email' | 'phone_voice' | 'slack' | 'sms' | 'whatsapp' | 'instagram' | 'messenger' | 'api' | 'web_voice';
    delivery_method: string;
    timestamp: string;
  }
}
```

***

`csat.score_submitted`

Triggered when a customer submits or updates a CSAT score for a conversation.

```typescript theme={"dark"}
{
  event: 'csat.score_submitted';
  organization_id: string;
  data: {
    csat_id: string;
    session_id: string;
    contact_id: string | null;
    score: number; // 1–5
    comment: string | null;
    channel: string;
    timestamp: string;
  }
}
```

***

## Phone Call Events

These events are fired for phone calls handled by AI phone agents (LiveKit). They apply to both inbound and outbound calls. When a phone call starts, a `conversation.started` event is co-fired alongside `phone_call.started`. Phone calls do **not** emit `conversation.closed` — its chat-centric shape (`closed_by`, `is_from_third_party`, `contact_reason`, …) doesn't map cleanly to a phone interaction. Listen on `phone_call.ended` instead, which carries the equivalent resolution and context fields below.

***

`phone_call.started`

Triggered when a phone call is initiated — either an inbound call is received or an outbound call is placed.
Also co-fires a `conversation.started` event.

```typescript theme={"dark"}
{
  event: 'phone_call.started';
  organization_id: string;
  data: {
    session_id: string;
    phone_agent_id: string;
    phone_agent_name: string;
    direction: 'inbound' | 'outbound';
    from_phone_number: string | null;
    to_phone_number: string | null;
    contact?: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
    timestamp: string;
  }
}
```

***

`phone_call.answered`

Triggered when the remote party picks up the call (SIP participant joined).

```typescript theme={"dark"}
{
  event: 'phone_call.answered';
  organization_id: string;
  data: {
    session_id: string;
    phone_agent_id: string;
    phone_agent_name: string;
    direction: 'inbound' | 'outbound';
    from_phone_number: string | null;
    to_phone_number: string | null;
    contact?: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
    timestamp: string;
  }
}
```

***

`phone_call.ended`

Triggered when a phone call ends normally. This event carries all end-of-call context — resolution status, who hung up, the resolved contact, the agent's configured languages, and the session's `custom_data` (SIP X-headers + call metadata) — so listeners do not need a separate `conversation.closed` event for phone calls.

| Field                     | Description                                                                                                                                                                                                                                                                                                                         |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `call_ended_by`           | Who hung up: `user` (contact), `agent` (AI agent, including transfers), `system` (e.g. inactivity timeout), or `null` when unknown (e.g. outbound calls that never connected).                                                                                                                                                      |
| `ai_resolution_status`    | Mirrors the persisted `chat_sessions.ai_closure_type`: `resolved` for completed calls, `handed_off` for transfers, or `null` for unanswered/failed calls.                                                                                                                                                                           |
| `human_resolution_status` | Always `null` for phone calls — there is no human-resolve path on a call.                                                                                                                                                                                                                                                           |
| `contact_id`              | The resolved contact's id, or `null` if no contact was matched.                                                                                                                                                                                                                                                                     |
| `contact`                 | Full contact record (id, name, email, phone, custom\_data) when `contact_id` is set.                                                                                                                                                                                                                                                |
| `language`                | The phone agent's configured language list (e.g. `["en"]`, `["es","en"]`), or `null` if no agent is attached.                                                                                                                                                                                                                       |
| `custom_data`             | The session's `custom_data` jsonb at end-of-call: incoming SIP X-headers (with the `X-` prefix stripped), values from the `X-OPENCX-SESSION-CUSTOM-DATA` header, plus call metadata (`duration_seconds`, `participant_count`, `is_outbound`, `recording_available`, `agent_version`, `call_status`, optional `knowledge_snapshot`). |

```typescript theme={"dark"}
{
  event: 'phone_call.ended';
  organization_id: string;
  data: {
    session_id: string;
    phone_agent_id: string;
    phone_agent_name: string;
    direction: 'inbound' | 'outbound';
    from_phone_number: string | null;
    to_phone_number: string | null;
    contact?: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
    timestamp: string;
    duration_seconds: number | null;
    outcome: 'completed' | 'unanswered' | 'transferred' | 'failed';
    recording_url: string | null;
    summary: string | null;
    call_ended_by: 'user' | 'agent' | 'system' | null;
    ai_resolution_status: 'resolved' | 'handed_off' | 'assumed_resolved' | null;
    human_resolution_status: null;
    contact_id: string | null;
    language: string[] | null;
    custom_data: object | null;
  }
}
```

***

`phone_call.transferred`

Triggered when the AI agent transfers a call to another destination (phone number, SIP URI, or graceful hangup).

```typescript theme={"dark"}
{
  event: 'phone_call.transferred';
  organization_id: string;
  data: {
    session_id: string;
    phone_agent_id: string;
    phone_agent_name: string;
    direction: 'inbound' | 'outbound';
    from_phone_number: string | null;
    to_phone_number: string | null;
    contact?: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
    timestamp: string;
    destination: {
      id: string;
      name: string;
      type: 'phone' | 'sip' | 'hangup';
    };
    reason: string | null;
  }
}
```

***

`phone_call.failed`

Triggered when a phone call fails to connect. The `failure_category` field indicates the type of failure.

| Category      | Description                                |
| ------------- | ------------------------------------------ |
| `no_answer`   | Contact didn't pick up                     |
| `rejected`    | Call was actively rejected                 |
| `busy`        | Line was busy                              |
| `trunk_error` | SIP trunk failure                          |
| `failed`      | Generic failure (timeout, SIP error, etc.) |

```typescript theme={"dark"}
{
  event: 'phone_call.failed';
  organization_id: string;
  data: {
    session_id: string;
    phone_agent_id: string;
    phone_agent_name: string;
    direction: 'inbound' | 'outbound';
    from_phone_number: string | null;
    to_phone_number: string | null;
    contact?: {
      id: string;
      name?: string;
      email?: string;
      phone_number?: string;
      custom_data?: object;
    };
    timestamp: string;
    failure_reason: string;
    failure_category: 'no_answer' | 'rejected' | 'busy' | 'trunk_error' | 'failed';
  }
}
```
