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

# WebSocket Events

> Reference for all client-to-server and server-to-client WebSocket events supported by the Zeplo real-time API.

# WebSocket Events

The Zeplo WebSocket API uses Socket.IO events for bi-directional communication. This page documents every event your application can emit and every event it should listen for.

## Client-to-Server Events

These events are sent from your application to the Zeplo server.

| Event | Description |
| - | - |
| `join_conversation` | Subscribe to updates for a specific conversation |
| `leave_conversation` | Unsubscribe from a conversation |
| `agent_join_conversation` | Mark an agent as active in a conversation |
| `agent_typing` | Indicate that an agent is typing a message |
| `agent_stopped_typing` | Indicate that an agent has stopped typing |
| `agent_send_message` | Send a message from an agent into a conversation |
| `message_read` | Mark a message as read by the agent |
| `close_conversation` | Close a conversation |
| `transfer_conversation` | Transfer a conversation to another agent or queue |

### join\_conversation

```javascript theme={null}
socket.emit("join_conversation", {
  conversationId: "conv_abc123",
});
```

### leave\_conversation

```javascript theme={null}
socket.emit("leave_conversation", {
  conversationId: "conv_abc123",
});
```

### agent\_join\_conversation

```javascript theme={null}
socket.emit("agent_join_conversation", {
  conversationId: "conv_abc123",
  agentId: "agent_xyz789",
});
```

### agent\_typing

```javascript theme={null}
socket.emit("agent_typing", {
  conversationId: "conv_abc123",
  agentId: "agent_xyz789",
});
```

### agent\_stopped\_typing

```javascript theme={null}
socket.emit("agent_stopped_typing", {
  conversationId: "conv_abc123",
  agentId: "agent_xyz789",
});
```

### agent\_send\_message

```javascript theme={null}
socket.emit("agent_send_message", {
  conversationId: "conv_abc123",
  agentId: "agent_xyz789",
  text: "Hello! How can I help you today?",
  attachments: [
    {
      type: "image",
      url: "https://example.com/product.jpg",
      caption: "Product catalog image",
    },
  ],
});
```

### message\_read

```javascript theme={null}
socket.emit("message_read", {
  conversationId: "conv_abc123",
  messageId: "msg_def456",
});
```

### close\_conversation

```javascript theme={null}
socket.emit("close_conversation", {
  conversationId: "conv_abc123",
  agentId: "agent_xyz789",
  resolution: "resolved",
  tags: ["support", "billing"],
});
```

### transfer\_conversation

```javascript theme={null}
socket.emit("transfer_conversation", {
  conversationId: "conv_abc123",
  fromAgentId: "agent_xyz789",
  toAgentId: "agent_lmn321",     // Optional: transfer to specific agent
  toQueueId: "queue_sales_01",   // Optional: transfer to a queue
  reason: "Escalation to sales team",
});
```

> [!NOTE]
>
> You must specify either `toAgentId` or `toQueueId` when transferring a conversation. If both are provided, `toAgentId` takes priority.

## Server-to-Client Events

These events are sent from the Zeplo server to your application.

| Event | Description |
| - | - |
| `new_message` | A new message was received in a conversation |
| `message_status_update` | The status of a previously sent message changed |
| `conversation_updated` | A conversation's metadata was updated |
| `agent_assigned` | An agent was assigned to or removed from a conversation |
| `user_typing` | The end user (WhatsApp contact) started typing |
| `user_stopped_typing` | The end user stopped typing |

### new\_message

```javascript theme={null}
socket.on("new_message", (data) => {
  console.log("New message:", data);
  // data = {
  //   conversationId: "conv_abc123",
  //   messageId: "msg_def456",
  //   from: "+1234567890",
  //   text: "Hi, I need help with my order",
  //   type: "text",
  //   attachments: [],
  //   timestamp: "2026-05-04T10:30:00Z",
  // }
});
```

### message\_status\_update

```javascript theme={null}
socket.on("message_status_update", (data) => {
  console.log("Status update:", data);
  // data = {
  //   messageId: "msg_def456",
  //   conversationId: "conv_abc123",
  //   status: "delivered",       // "sent" | "delivered" | "read" | "failed"
  //   timestamp: "2026-05-04T10:30:05Z",
  // }
});
```

### conversation\_updated

```javascript theme={null}
socket.on("conversation_updated", (data) => {
  console.log("Conversation updated:", data);
  // data = {
  //   conversationId: "conv_abc123",
  //   status: "open",            // "open" | "closed" | "pending"
  //   assignedAgentId: "agent_xyz789",
  //   tags: ["support", "billing"],
  //   updatedAt: "2026-05-04T10:30:00Z",
  // }
});
```

### agent\_assigned

```javascript theme={null}
socket.on("agent_assigned", (data) => {
  console.log("Agent assigned:", data);
  // data = {
  //   conversationId: "conv_abc123",
  //   agentId: "agent_xyz789",
  //   agentName: "Sarah Connor",
  //   action: "assigned",        // "assigned" | "unassigned"
  // }
});
```

### user\_typing

```javascript theme={null}
socket.on("user_typing", (data) => {
  console.log("User is typing:", data);
  // data = {
  //   conversationId: "conv_abc123",
  //   userId: "+1234567890",
  // }
});
```

### user\_stopped\_typing

```javascript theme={null}
socket.on("user_stopped_typing", (data) => {
  console.log("User stopped typing:", data);
  // data = {
  //   conversationId: "conv_abc123",
  //   userId: "+1234567890",
  // }
});
```

## Rooms

Events are scoped to rooms. You must join the appropriate room before you will receive events.

| Room | Format | Description |
| - | - | - |
| Conversation | `conversation:{id}` | Events for a single conversation |
| Site | `site:{id}` | Events for all conversations under a site |
| User | `user:{id}` | Events scoped to a specific user/agent |

### Joining Rooms

Rooms are joined automatically when you emit `join_conversation`. For site-level and user-level rooms, connect with the appropriate authentication context:

```javascript theme={null}
// Joining a conversation room
socket.emit("join_conversation", {
  conversationId: "conv_abc123",
});

// The server automatically subscribes you to the conversation:conv_abc123 room.
// All new_message, user_typing, and other conversation events will be pushed to you.
```

```javascript theme={null}
// Site-level subscription happens at connection time
// based on your authenticated user's site membership.
// All conversations under site:site_456 will emit events to you.
```

> [!WARNING]
>
> You can be subscribed to a maximum of 500 conversation rooms per WebSocket connection. Use site-level rooms if you need broader coverage.

## Best Practices

> **Handling reconnections**
>
> Socket.IO reconnects automatically, but room subscriptions are lost on reconnection. Re-subscribe to all active rooms after a `connect` event:
>
> ```javascript theme={null}
> const activeRooms = new Set();
>
> function joinRoom(conversationId) {
>   socket.emit("join_conversation", { conversationId });
>   activeRooms.add(conversationId);
> }
>
> socket.on("connect", () => {
>   // Re-subscribe to all rooms after reconnection
>   for (const conversationId of activeRooms) {
>     socket.emit("join_conversation", { conversationId });
>   }
> });
> ```

> **Heartbeat and keepalive**
>
> Socket.IO manages heartbearts automatically. The default ping interval is 25 seconds with a 20-second timeout. If you need to customize these values:
>
> ```javascript theme={null}
> const socket = io("wss://zeplo.ai", {
>   auth: { token: "your_api_key" },
>   transports: ["websocket"],
>   pingInterval: 25000,
>   pingTimeout: 20000,
> });
> ```
>
> Do not send custom ping events — Socket.IO handles this for you.

> **Error handling**
>
> Always listen for error events to catch permission issues and validation failures:
>
> ```javascript theme={null}
> socket.on("error", (error) => {
>   console.error("Socket error:", error);
> });
>
> // Many events return acknowledgment callbacks with errors
> socket.emit("agent_send_message", payload, (response) => {
>   if (response.error) {
>     console.error("Failed to send message:", response.error);
>   } else {
>     console.log("Message sent:", response.messageId);
>   }
> });
> ```

> **Room management**
>
> Leave rooms when they are no longer needed to reduce unnecessary event traffic. For example, when an agent closes a conversation tab in your UI:
>
> ```javascript theme={null}
> // When agent navigates away from a conversation
> function cleanupRoom(conversationId) {
>   socket.emit("leave_conversation", { conversationId });
>   activeRooms.delete(conversationId);
> }
>
> // On page unload
> window.addEventListener("beforeunload", () => {
>   for (const conversationId of activeRooms) {
>     socket.emit("leave_conversation", { conversationId });
>   }
> });
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.