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

# Socket.IO Events

> Real-time messaging, voice calls, and notification events for Helpdesk integration

export const api_base_url = "https://api.eusate.com";

## Overview

Socket.IO enables real-time bidirectional communication between your application and the Helpdesk system. This allows instant messaging, voice calls, live notifications, and seamless customer support experiences.

## Connection

### Authentication

Connect to the Socket.IO server using your API key:

```javascript theme={null}
const API_BASE_URL = '{api_base_url}';
const socket = io(`${API_BASE_URL}/helpdesk?token_source=api&token=YOUR_API_KEY`);
```

<Warning>
  Never expose your API key in client-side code. Fetch it from your backend and pass it to the socket connection.
</Warning>

### Connection Response

Upon successful connection, you'll receive:

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "Connected",
  "data": {}
}
```

If authentication fails, the connection is refused with `status_code` 400 or 401.

### Response Envelope

All event responses follow this envelope:

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "Human-readable status",
  "data": {}
}
```

## Event Flow

<Steps>
  <Step title="Connect">
    Authenticate and establish a connection using your API key.
  </Step>

  <Step title="Enter Customer Room">
    Call `enter_customer_support` with the customer's session ID immediately after connecting. This subscribes the customer to their personal room so they can receive messages from agents even before a conversation starts.
  </Step>

  <Step title="Send & Receive Messages">
    Use `support` to send customer messages. Listen on `support` for SATE (AI) replies and on `customer_support` for agent replies.
  </Step>

  <Step title="Join Conversation Room">
    After the first message returns a `ticket_chat_id`, call `enter_support` to subscribe to that conversation room for all subsequent broadcasts.
  </Step>

  <Step title="Mark Read">
    Call `mark_conversation_read` whenever the customer views messages.
  </Step>

  <Step title="Voice Calls (optional)">
    Use `call_support` to start a voice call with SATE and `close_call_support` to end it.
  </Step>

  <Step title="Close & Review">
    When resolved, call `close_support` to end the conversation and `review_support` to collect feedback.
  </Step>
</Steps>

***

## Events Reference

### Emitting Events (Client → Server)

***

#### Enter Customer Room

Subscribe the customer to their personal room. Call this immediately after connecting so the customer can receive agent messages (`customer_support`) and auto-close notifications (`customer_support_closed`) regardless of which conversation is active.

**Event Name:** `enter_customer_support`

##### Request Schema

<ParamField path="session_id" type="string" required>
  Unique identifier for the customer — use the same value you send in the `support` event
</ParamField>

##### Example Request

```javascript theme={null}
socket.on('connect', () => {
  socket.emit('enter_customer_support', {
    session_id: 'customer_123'
  });
});
```

##### Response Schema

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "Joined customer support room",
  "data": {}
}
```

***

#### Enter Conversation Room

Join a specific conversation room to receive real-time updates for that conversation.

**Event Name:** `enter_support`

<Info>
  Call this after the first `support` response returns a `ticket_chat_id`. You only need to call it once per conversation.
</Info>

##### Request Schema

<ParamField path="ticket_chat_id" type="string" required>
  The conversation ID returned from the first `support` response
</ParamField>

##### Example Request

```javascript theme={null}
socket.emit('enter_support', {
  ticket_chat_id: 'conv_abc123'
});
```

##### Response Schema

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "Joined support room",
  "data": {}
}
```

***

#### Leave Conversation Room

Unsubscribe from a conversation room.

**Event Name:** `leave_support`

##### Request Schema

<ParamField path="ticket_chat_id" type="string" required>
  The conversation ID to leave
</ParamField>

##### Example Request

```javascript theme={null}
socket.emit('leave_support', {
  ticket_chat_id: 'conv_abc123'
});
```

##### Response Schema

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "Left support room",
  "data": {}
}
```

***

#### Send Message

The primary event for sending customer messages. SATE (AI) replies come back on the `support` event; agent replies come on `customer_support`.

**Event Name:** `support`

##### Request Schema

<ParamField path="session_id" type="string" required>
  Unique identifier for the customer
</ParamField>

<ParamField path="message" type="string" required>
  The message content to send
</ParamField>

<ParamField path="attachment" type="boolean" required>
  Whether this message includes an attachment
</ParamField>

<ParamField path="ticket_chat_id" type="string">
  Conversation ID. Set to `null` for the first message in a new conversation; use the returned ID for all follow-ups.
</ParamField>

<ParamField path="attachment_metadata" type="object | array">
  Attachment details. Required when `attachment` is `true`.
</ParamField>

##### Example Request

```javascript theme={null}
// Start a new conversation
socket.emit('support', {
  session_id: 'customer_123',
  message: 'Hello, I need help with my order',
  attachment: false,
  ticket_chat_id: null
});

// Continue an existing conversation
socket.emit('support', {
  session_id: 'customer_123',
  message: 'My order number is #4567',
  attachment: false,
  ticket_chat_id: 'conv_abc123'
});
```

##### Response Schema

After sending, the server emits two separate events:

**1. `support_echo`** — Acknowledges that your message was received and queued:

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "support_echo",
  "data": {}
}
```

**2. `support`** — The AI or agent reply, broadcast to the conversation room:

<ResponseField name="status_code" type="integer">
  200 for success
</ResponseField>

<ResponseField name="sid" type="string">
  Session identifier
</ResponseField>

<ResponseField name="message" type="string">
  Status message
</ResponseField>

<ResponseField name="data" type="object">
  Response data
</ResponseField>

**`data` object:**

<ResponseField name="message" type="string">
  The reply message from SATE or an agent
</ResponseField>

<ResponseField name="ticket_chat_id" type="string">
  Conversation ID — store this from the first response to continue the conversation
</ResponseField>

<ResponseField name="sender" type="string">
  Who sent the reply: `"sate"` or `"agent"`
</ResponseField>

<ResponseField name="attachment" type="boolean">
  Whether the reply includes an attachment
</ResponseField>

<ResponseField name="attachment_metadata" type="object | null">
  Attachment details if present
</ResponseField>

<ResponseField name="close_support" type="boolean">
  `true` when SATE signals that the issue appears resolved and the conversation can be closed
</ResponseField>

<ResponseField name="closed_support" type="boolean">
  `true` when the conversation has been confirmed closed
</ResponseField>

<ResponseField name="date_created" type="string">
  ISO 8601 timestamp
</ResponseField>

<ResponseField name="date_updated" type="string">
  ISO 8601 timestamp
</ResponseField>

##### Example Response

```javascript theme={null}
socket.on('support', (response) => {
  // {
  //   "status_code": 200,
  //   "sid": "socket_session_id",
  //   "message": "Message sent successfully",
  //   "data": {
  //     "message": "Happy to help! Could you share your order number?",
  //     "ticket_chat_id": "conv_abc123",
  //     "sender": "sate",
  //     "attachment": false,
  //     "attachment_metadata": null,
  //     "close_support": false,
  //     "closed_support": false,
  //     "date_created": "2024-01-15T10:31:00Z",
  //     "date_updated": "2024-01-15T10:31:00Z"
  //   }
  // }
});
```

<Info>
  When `close_support` is `true` but `closed_support` is `false`, SATE is suggesting the issue looks resolved. Ask the customer to confirm, then call `close_support` if they agree.
</Info>

##### Important Notes

* **New conversations**: Set `ticket_chat_id` to `null`. Save the `ticket_chat_id` from the response — you need it for every follow-up message.
* **Conversation limit**: Each customer session can have a maximum of 3 open conversations at a time.
* **Attachments**: Only available when the responder is `"agent"`. SATE does not send attachments.

***

#### Mark Conversation Read

Mark all messages in a conversation as read by the customer.

**Event Name:** `mark_conversation_read`

##### Request Schema

<ParamField path="ticket_chat_id" type="string" required>
  The conversation ID to mark as read
</ParamField>

<ParamField path="entity" type="string" required>
  Who is marking as read. Always use `"customer"` for customer-side integrations.
</ParamField>

##### Example Request

```javascript theme={null}
socket.emit('mark_conversation_read', {
  ticket_chat_id: 'conv_abc123',
  entity: 'customer'
});
```

##### Response Schema

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "Marked as read",
  "data": {}
}
```

***

#### Start Voice Call

Initiate a voice call with SATE. The server responds with a LiveKit token you use to join the audio room.

**Event Name:** `call_support`

##### Request Schema

<ParamField path="session_id" type="string" required>
  Unique identifier for the customer
</ParamField>

<ParamField path="ticket_chat_id" type="string">
  Existing conversation ID. Set to `null` to start a new conversation via call.
</ParamField>

##### Example Request

```javascript theme={null}
// Start a call on a new conversation
socket.emit('call_support', {
  session_id: 'customer_123',
  ticket_chat_id: null
});

// Start a call on an existing conversation
socket.emit('call_support', {
  session_id: 'customer_123',
  ticket_chat_id: 'conv_abc123'
});
```

##### Response Schema

The server broadcasts a `call_support` event to the conversation room with the call details:

<ResponseField name="token" type="string">
  LiveKit access token — use this with the LiveKit SDK to join the voice room
</ResponseField>

<ResponseField name="room" type="string">
  LiveKit room name (matches the `ticket_chat_id`)
</ResponseField>

<ResponseField name="ticket_chat_id" type="string">
  Conversation ID
</ResponseField>

<ResponseField name="ticket_call_id" type="string">
  Unique ID for this call
</ResponseField>

<ResponseField name="call_start_time" type="string">
  ISO 8601 timestamp of when the call started
</ResponseField>

##### Example Response

```javascript theme={null}
socket.on('call_support', (response) => {
  if (response.status_code === 200) {
    const { token, room } = response.data;
    // Connect to the voice room using the LiveKit client SDK
    connectToLiveKitRoom(token, room);
  }
});
```

##### Important Notes

* **Availability**: Voice calls are only available while SATE is the active responder and no other call is in progress.
* **Voice room**: Pass the `token` to the [LiveKit client SDK](https://docs.livekit.io/realtime/client/) to connect to the audio room.
* **Room name**: The LiveKit room name is the same as `ticket_chat_id`.

***

#### End Voice Call

End an active voice call.

**Event Name:** `close_call_support`

##### Request Schema

<ParamField path="ticket_chat_id" type="string" required>
  The conversation ID for the active call
</ParamField>

##### Example Request

```javascript theme={null}
socket.emit('close_call_support', {
  ticket_chat_id: 'conv_abc123'
});
```

##### Response Schema

The server broadcasts a `close_call_support` event to the conversation room:

<ResponseField name="room" type="string">
  LiveKit room name
</ResponseField>

<ResponseField name="ticket_chat_id" type="string">
  Conversation ID
</ResponseField>

<ResponseField name="ticket_call_id" type="string | null">
  Call ID — `null` if no active call was found
</ResponseField>

<ResponseField name="call_start_time" type="string">
  ISO 8601 call start timestamp
</ResponseField>

<ResponseField name="call_end_time" type="string">
  ISO 8601 call end timestamp
</ResponseField>

##### Example Response

```javascript theme={null}
socket.on('close_call_support', (response) => {
  if (response.status_code === 200) {
    const { call_start_time, call_end_time } = response.data;
    disconnectFromLiveKit();
    showCallSummary(call_start_time, call_end_time);
  }
});
```

***

#### Close Conversation

End a conversation when the issue is resolved.

**Event Name:** `close_support`

<Info>
  Call this only after SATE sets `close_support: true` in a message and the customer confirms they want to end the conversation.
</Info>

##### Request Schema

<ParamField path="ticket_chat_id" type="string" required>
  The conversation ID to close
</ParamField>

##### Example Request

```javascript theme={null}
socket.emit('close_support', {
  ticket_chat_id: 'conv_abc123'
});
```

##### Response Schema

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "Support conversation closed",
  "data": {
    "message": "Thank you for using our support. Your conversation has been closed.",
    "ticket_chat_id": "conv_abc123",
    "sender": "sate",
    "attachment": false,
    "attachment_metadata": null,
    "close_support": true,
    "closed_support": true,
    "date_created": "2024-01-15T12:00:00Z",
    "date_updated": "2024-01-15T12:00:00Z"
  }
}
```

***

#### Review Support

Collect customer feedback after a conversation ends.

**Event Name:** `review_support`

##### Request Schema

<ParamField path="ticket_chat_id" type="string" required>
  The conversation ID to review
</ParamField>

<ParamField path="rating" type="string" required>
  Customer rating: `"1"`, `"2"`, `"3"`, `"4"`, or `"5"`
</ParamField>

<ParamField path="review" type="string">
  Optional customer feedback text
</ParamField>

##### Example Request

```javascript theme={null}
socket.emit('review_support', {
  ticket_chat_id: 'conv_abc123',
  rating: '5',
  review: 'Great service, very helpful!'
});
```

##### Response Schema

```json theme={null}
{
  "status_code": 200,
  "sid": "socket_session_id",
  "message": "Review noted",
  "data": {
    "rating": "5",
    "review": "Great service, very helpful!",
    "ticket_chat_id": "conv_abc123"
  }
}
```

***

### Server Broadcasts (Server → Client)

These are events the server pushes to your client. Set up listeners for them to handle real-time updates.

***

#### Agent Message Received

Fired when a human agent sends a reply. Agent messages arrive on this event — not on `support` — so you must listen on both.

**Listen On:** `customer_support`

<Info>
  This event is delivered to the customer's personal room (set up via `enter_customer_support`), so it works even before the customer has joined a specific conversation room.
</Info>

##### Response Schema

<ResponseField name="message" type="string">
  Agent's reply message
</ResponseField>

<ResponseField name="ticket_chat_id" type="string">
  Conversation ID
</ResponseField>

<ResponseField name="sender" type="string">
  Always `"agent"` for this event
</ResponseField>

<ResponseField name="attachment" type="boolean">
  Whether the message includes an attachment
</ResponseField>

<ResponseField name="attachment_metadata" type="object | null">
  Attachment details if present
</ResponseField>

<ResponseField name="close_support" type="boolean">
  Whether the agent is suggesting closure
</ResponseField>

<ResponseField name="closed_support" type="boolean">
  Whether the conversation is closed
</ResponseField>

<ResponseField name="date_created" type="string">
  ISO 8601 timestamp
</ResponseField>

<ResponseField name="date_updated" type="string">
  ISO 8601 timestamp
</ResponseField>

##### Example

```javascript theme={null}
socket.on('customer_support', (response) => {
  if (response.status_code === 200) {
    const { message, sender, ticket_chat_id } = response.data;
    displayMessage(message, sender);
    socket.emit('mark_conversation_read', {
      ticket_chat_id,
      entity: 'customer'
    });
  }
});
```

***

#### Conversation Auto-Closed

Fired when SATE closes a conversation automatically — without waiting for the customer to confirm. Update your UI to reflect the closed state.

**Listen On:** `customer_support_closed`

##### Response Schema

<ResponseField name="ticket_id" type="string">
  The parent ticket ID
</ResponseField>

<ResponseField name="ticket_chat_id" type="string">
  The conversation ID that was closed
</ResponseField>

<ResponseField name="status" type="string">
  Updated ticket status
</ResponseField>

##### Example

```javascript theme={null}
socket.on('customer_support_closed', (response) => {
  if (response.status_code === 200) {
    const { ticket_chat_id } = response.data;
    showConversationClosed(ticket_chat_id);
  }
});
```

***

#### Handler Changed

Fired when a human agent takes over a conversation from SATE. Use this to show a notification like "You are now chatting with a human agent."

**Listen On:** `support_handler_changed`

##### Response Schema

<ResponseField name="ticket_chat_id" type="string">
  The conversation ID
</ResponseField>

<ResponseField name="handler" type="string">
  The new handler type: `"agent"`
</ResponseField>

<ResponseField name="agent" type="object">
  Details of the agent who took over
</ResponseField>

**`agent` object:**

<ResponseField name="id" type="string">
  Agent's unique ID
</ResponseField>

<ResponseField name="username" type="string">
  Agent's display name
</ResponseField>

##### Example

```javascript theme={null}
socket.on('support_handler_changed', (response) => {
  if (response.status_code === 200) {
    const { handler, agent } = response.data;
    if (handler === 'agent') {
      showNotification(`You're now chatting with ${agent.username}`);
    }
  }
});
```

***

#### SATE Left Call

Fired when SATE disconnects from an active voice call. Handle this to update your call UI and give the customer the option to end or wait.

**Listen On:** `sate_left_call`

##### Response Schema

<ResponseField name="ticket_chat_id" type="string">
  The conversation ID
</ResponseField>

<ResponseField name="ticket_call_id" type="string">
  The call ID
</ResponseField>

##### Example

```javascript theme={null}
socket.on('sate_left_call', (response) => {
  if (response.status_code === 200) {
    disconnectFromLiveKit();
    showNotification('The call has ended');
  }
});
```

***

## Implementation Example

A complete example showing the full conversation and call flow:

```javascript theme={null}
const API_BASE_URL = '{api_base_url}';
const socket = io(`${API_BASE_URL}/helpdesk?token_source=api&token=${API_KEY}`);

let currentConversationId = null;

// 1. On connect — immediately subscribe to the customer's personal room
socket.on('connect', () => {
  socket.emit('enter_customer_support', {
    session_id: CUSTOMER_SESSION_ID
  });
});

// 2. Send a message
function sendMessage(message, attachmentData = null) {
  socket.emit('support', {
    session_id: CUSTOMER_SESSION_ID,
    message: message,
    attachment: !!attachmentData,
    attachment_metadata: attachmentData,
    ticket_chat_id: currentConversationId
  });
}

// 3. Receive SATE replies
socket.on('support', (response) => {
  if (response.status_code === 200) {
    const { data } = response;

    if (!currentConversationId) {
      currentConversationId = data.ticket_chat_id;
      // Join the conversation room for all future broadcasts
      socket.emit('enter_support', { ticket_chat_id: currentConversationId });
    }

    displayMessage(data.message, data.sender);

    socket.emit('mark_conversation_read', {
      ticket_chat_id: currentConversationId,
      entity: 'customer'
    });

    // SATE suggests the issue is resolved — ask the customer to confirm
    if (data.close_support && !data.closed_support) {
      askCustomerToClose(currentConversationId);
    }
  }
});

// 4. Receive agent replies (separate event from SATE replies)
socket.on('customer_support', (response) => {
  if (response.status_code === 200) {
    displayMessage(response.data.message, response.data.sender);
    socket.emit('mark_conversation_read', {
      ticket_chat_id: response.data.ticket_chat_id,
      entity: 'customer'
    });
  }
});

// 5. Notify customer when a human agent takes over
socket.on('support_handler_changed', (response) => {
  if (response.status_code === 200 && response.data.handler === 'agent') {
    showBanner(`You're now chatting with ${response.data.agent.username}`);
  }
});

// 6. Handle auto-close by SATE
socket.on('customer_support_closed', (response) => {
  if (response.status_code === 200) {
    showConversationClosed(response.data.ticket_chat_id);
  }
});

// 7. Voice call
function startVoiceCall() {
  socket.emit('call_support', {
    session_id: CUSTOMER_SESSION_ID,
    ticket_chat_id: currentConversationId
  });
}

socket.on('call_support', (response) => {
  if (response.status_code === 200) {
    // Use the LiveKit SDK to connect to the voice room
    connectToLiveKitRoom(response.data.token, response.data.room);
  }
});

socket.on('sate_left_call', () => {
  disconnectFromLiveKit();
  showNotification('The call has ended');
});

function endVoiceCall() {
  socket.emit('close_call_support', {
    ticket_chat_id: currentConversationId
  });
}

socket.on('close_call_support', (response) => {
  if (response.status_code === 200) {
    disconnectFromLiveKit();
    showCallSummary(response.data.call_start_time, response.data.call_end_time);
  }
});

// 8. Customer confirms close
function closeConversation() {
  socket.emit('close_support', { ticket_chat_id: currentConversationId });
}

// 9. Collect review
function submitReview(rating, feedback) {
  socket.emit('review_support', {
    ticket_chat_id: currentConversationId,
    rating: rating,
    review: feedback
  });
}
```

## Next Steps

* [Complete API Reference](/modules/helpdesk/api-reference) for HTTP endpoints
* [SDK Integration](/modules/helpdesk/sdk) for easier implementation
