Chat WebSocket Documentation

Table of Contents

1. Overview

The Chat WebSocket system enables real-time communication between users. The system provides:

📸 Image Sharing: For comprehensive image sharing documentation with code examples, see Chat Image Sharing Guide.

2. Architecture

2.1 Components

2.2 Flow Diagram

User → WebSocket Connection → JWT Auth → Participant Check → Join Group → Ready
                                                                    ↓
User sends message → Save to DB → Broadcast to Group → All participants receive
        

3. Authentication

WebSocket connections require JWT authentication. The token must be provided in one of two ways:

3.1 Query String (Recommended)

Connection URL:
ws://api.qsocial.net/ws/chat/{room_id}/?token={{jwt_token}}

3.2 Authorization Header

Some WebSocket clients support custom headers:

Authorization: Bearer {{jwt_token}}
Note: Not all WebSocket implementations support custom headers. Using query string is more reliable across different platforms.

4. Understanding Room ID

4.1 What is Room ID?

The room_id is the unique identifier (primary key) of a ChatRoom instance. It's a numeric value that uniquely identifies a chat room between users. You need the room_id to:

Important: The room_id is returned as id in all chat room API responses.

4.2 How to Get Room ID

Method 1: Create a New Chat Room

When you create a direct chat, the response includes the room_id:

Endpoint: POST /api/chats/
Request Body:
{
    "user_id": 123
}
Response:
{
    "id": 456,  // ← This is the room_id
    "participants": [
        {"id": 1, "username": "current_user"},
        {"id": 123, "username": "other_user"}
    ],
    "is_group": false,
    "created_at": "2024-03-20T10:00:00Z",
    "unread_count": 0
}

Note: If a chat room already exists between you and the specified user, the existing room is returned (no new room is created).

Method 2: List Your Chat Rooms

Get all chat rooms you're part of. Each room in the list has an id field:

Endpoint: GET /api/chats/
Response:
{
    "count": 5,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 456,  // ← room_id
            "participants": [
                {"id": 1, "username": "user1"},
                {"id": 123, "username": "user2"}
            ],
            "last_message": {
                "id": 789,
                "content": "Hello!",
                "created_at": "2024-03-20T10:00:00Z"
            },
            "unread_count": 3,
            "is_group": false
        },
        {
            "id": 789,  // ← another room_id
            "participants": [...],
            "last_message": Ellipsis,
            "unread_count": 0,
            "is_group": false
        }
    ]
}

Method 3: Get Specific Room Details

If you already know the room_id, you can fetch that room's details directly:

Endpoint: GET /api/chats/{room_id}/
Example: GET /api/chats/456/

4.3 Using Room ID

Once you have the room_id, you can use it for various operations:

Send a Message

Endpoint: POST /api/chats/{room_id}/messages/
Example: POST /api/chats/456/messages/
Request Body:
{
    "content": "Hello, this is a test message!"
}

Get Room Messages

Endpoint: GET /api/chats/{room_id}/
Example: GET /api/chats/456/

Mark Messages as Read

Endpoint: POST /api/chats/{room_id}/mark-read/
Example: POST /api/chats/456/mark-read/

Connect to WebSocket

WebSocket URL:
ws://api.qsocial.net/ws/chat/{room_id}/?token={{jwt_token}}
Example:
ws://api.qsocial.net/ws/chat/456/?token=eyJ0eXAiOiJKV1QiLCJhbGc...

4.4 Complete Example: Getting and Using Room ID

// Step 1: Create or get chat room
const response = await fetch('http://localhost:8000/api/chats/', {
    method: 'POST',
    headers: {
        'Authorization': 'Bearer YOUR_TOKEN',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        user_id: 123
    })
});

const room = await response.json();
const roomId = room.id;  // Extract room_id
console.log('Room ID:', roomId);

// Step 2: Use room_id to send a message
await fetch(`http://localhost:8000/api/chats/${roomId}/messages/`, {
    method: 'POST',
    headers: {
        'Authorization': 'Bearer YOUR_TOKEN',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        content: 'Hello!'
    })
});

// Step 3: Connect to WebSocket using room_id
const ws = new WebSocket(
    `ws://localhost:8000/ws/chat/${roomId}/?token=${token}`
);

5. WebSocket Connection

5.1 Connection URL

WebSocket Endpoint:
ws://api.qsocial.net/ws/chat/{room_id}/

Production (WSS):
wss://api.qsocial.net/ws/chat/{room_id}/

5.2 Connection Requirements

5.3 Connection Status Codes

Code Meaning Description
1000 Normal Closure Connection closed normally
4001 Unauthorized User is not authenticated or token is invalid
4003 Forbidden User is not a participant in this chat room
1011 Internal Error Server error during connection

6. WebSocket Events

All WebSocket messages are JSON objects with a type field that identifies the event type. This section documents all events you can send (trigger) and all events you will receive (listen to).

6.1 Events to Trigger (Send to Server)

These are events you send to the server via WebSocket:

6.1.1 Send Chat Message

Event Type: chat_message
Purpose: Send a text message or message with attachment to the chat room
Payload to Send (Text Only):
{
    "type": "chat_message",
    "content": "Your message text here"
}
Payload to Send (With Attachment URL):
{
    "type": "chat_message",
    "content": "Check out this image!",
    "attachment_url": "https://example.com/files/image.jpg",
    "attachment_type": "image",
    "attachment_name": "image.jpg"
}
Payload to Send (With Base64 Attachment):
{
    "type": "chat_message",
    "content": "Here's a small image",
    "attachment_base64": "iVBORw0KGgoAAAANSUhEUgAA...",
    "attachment_type": "image",
    "attachment_name": "photo.jpg"
}
Field Descriptions:
Note: Either content or an attachment (attachment_url or attachment_base64) is required. Empty messages without attachments are ignored.
Attachment Best Practices:
  • For large files (>1MB), upload via REST API first and use attachment_url
  • For small files (<1MB), you can use attachment_base64 directly
  • Supported file types: Images (JPEG, PNG, GIF, WebP), Videos (MP4, MPEG, MOV, AVI), Documents (PDF, DOC, DOCX, TXT), Audio (MP3, WAV, OGG)
  • Maximum file size: 5MB

6.1.2 Send Typing Indicator

Event Type: typing
Purpose: Notify other participants that you are typing
Payload to Send:
{
    "type": "typing"
}
Field Descriptions:
Best Practice: Debounce typing indicators (send every 2-3 seconds while typing) to avoid flooding the server.

6.1.3 Send Read Receipt

Event Type: read_receipt
Purpose: Mark a specific message as read
Payload to Send:
{
    "type": "read_receipt",
    "message_id": "123"
}
Field Descriptions:
Note: The message must exist in the current chat room and the read receipt will be saved to the database.

6.2 Events to Listen (Receive from Server)

These are events you receive from the server via WebSocket:

6.2.1 Receive Chat Message

Event Type: chat_message
Purpose: Receive a new message from another participant
Payload Received (Text Only):
{
    "type": "chat_message",
    "content": "Message content",
    "user_id": 7,
    "username": "john_doe",
    "full_name": "John Doe",
    "message_id": "123",
    "timestamp": "2025-11-15T18:23:21.483664+00:00"
}
Payload Received (With Attachment):
{
    "type": "chat_message",
    "content": "Check out this image!",
    "user_id": 7,
    "username": "john_doe",
    "full_name": "John Doe",
    "message_id": "123",
    "timestamp": "2025-11-15T18:23:21.483664+00:00",
    "attachment_url": "https://api.qsocial.net/media/chat/attachments/abc123.jpg",
    "attachment_type": "image",
    "attachment_name": "photo.jpg",
    "attachment_size": 245678
}
Field Descriptions:
Note: You will NOT receive your own messages back through WebSocket. Messages you send are saved to the database but not echoed back.
Handling Attachments:
  • Check for attachment_url field to determine if message has an attachment
  • Use attachment_type to determine how to display the attachment (image viewer, video player, download link, etc.)
  • Download the file from attachment_url to display or save locally
  • Show file size using attachment_size for user information

6.2.2 User Online Event

Event Type: user_online
Purpose: Notify when a participant comes online
Payload Received:
{
    "type": "user_online",
    "user_id": 7,
    "username": "john_doe",
    "full_name": "John Doe"
}
Field Descriptions:
Note: This event is automatically sent when a user connects to the WebSocket. You will receive this for all other participants when they connect.

6.2.3 User Offline Event

Event Type: user_offline
Purpose: Notify when a participant goes offline
Payload Received:
{
    "type": "user_offline",
    "user_id": 7,
    "username": "john_doe"
}
Field Descriptions:
Note: This event is automatically sent when a user disconnects from the WebSocket (closes connection, network error, etc.).

6.2.4 User Typing Event

Event Type: user_typing
Purpose: Notify when another participant is typing
Payload Received:
{
    "type": "user_typing",
    "user_id": 7,
    "username": "john_doe",
    "full_name": "John Doe"
}
Field Descriptions:
Note: You will NOT receive your own typing indicators back. This event is only for other participants.

6.2.5 Message Read Event

Event Type: message_read
Purpose: Notify when another participant marks a message as read
Payload Received:
{
    "type": "message_read",
    "message_id": "123",
    "user_id": 7,
    "username": "john_doe"
}
Field Descriptions:
Note: You will NOT receive read receipts for messages you mark as read yourself. This event is only for other participants' read receipts.

6.2.6 Error Event

Event Type: error
Purpose: Notify about errors that occurred during message processing
Payload Received:
{
    "type": "error",
    "message": "Error processing message"
}
Field Descriptions:
Common Errors:
  • "Invalid JSON" - The message you sent was not valid JSON
  • "Error processing message" - Server-side error occurred while processing your message

6.3 Event Flow Summary

Event Type Direction When Who Receives
chat_message Send → Receive When you send a message All other participants in the room
typing Send When you want to show typing indicator N/A (triggers user_typing event)
user_typing Receive When another participant sends typing indicator All other participants
read_receipt Send When you mark a message as read N/A (triggers message_read event)
message_read Receive When another participant marks a message as read All other participants
user_online Receive When a participant connects All participants in the room
user_offline Receive When a participant disconnects All participants in the room
error Receive When an error occurs processing your message Only the sender of the problematic message

7. REST API Endpoints

7.1 List Chat Rooms

GET /api/chats/
Returns list of chat rooms for authenticated user

Query Parameters:

7.2 Create Direct Chat

POST /api/chats/
Body:
{
    "user_id": 123,
    "post_id": 456  // Optional: if chat initiated from a post
}

7.3 Get Chat Room Details

GET /api/chats/{room_id}/
Returns detailed information about a specific chat room with paginated messages

7.4 Send Message (HTTP)

POST /api/chats/{room_id}/messages/
Send a text message or message with attachment to a chat room

Request Body (Text Only - JSON):
{
    "content": "Your message here"
}
Request Body (With Attachment - multipart/form-data):
Content-Type: multipart/form-data

content: "Check out this image!"
attachment: [file binary data]
📸 Image Sharing: For detailed image sharing examples and best practices, see the Chat Image Sharing Guide.
Field Descriptions: Supported File Types: File Size Limits: Response (Success - 201):
{
    "id": 123,
    "sender": {
        "id": 7,
        "username": "john_doe",
        "full_name": "John Doe"
    },
    "content": "Check out this image!",
    "created_at": "2025-11-15T18:23:21.483664+00:00",
    "is_read": false,
    "attachment_url": "https://api.qsocial.net/media/chat/attachments/abc123.jpg",
    "attachment_type": "image",
    "attachment_name": "photo.jpg",
    "attachment_size": 245678
}
Best Practices:
  • For file uploads, use multipart/form-data content type
  • Include both content and attachment if you want to send a message with a caption
  • For large files, consider uploading via REST API and then sending the URL via WebSocket
  • The attachment type is automatically detected from the file's content type
Note: Either content or attachment must be provided. Empty messages without attachments will be rejected.

7.5 Delete Message

DELETE /api/chats/messages/{message_id}/
Soft deletes a message (only sender can delete)

7.6 Mark Messages Read

POST /api/chats/{room_id}/mark-read/
Marks all messages in the room as read for the authenticated user

8. Error Handling

8.1 Connection Errors

Error Cause Solution
Connection Refused Server not running or wrong URL Verify server is running and URL is correct
401 Unauthorized Invalid or expired JWT token Refresh token and reconnect
403 Forbidden User not a participant Verify user has access to chat room
500 Internal Error Server-side error Check server logs, retry connection

8.2 Message Errors

Empty Content: Messages with empty content are ignored. Always validate content before sending.

8.3 Reconnection Strategy

// Exponential backoff reconnection
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;

function reconnect() {
  if (reconnectAttempts >= maxReconnectAttempts) {
    console.error('Max reconnection attempts reached');
    return;
  }

  const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
  reconnectAttempts++;

  setTimeout(() => {
    console.log(`Reconnecting (attempt ${reconnectAttempts})...`);
    connect();
  }, delay);
}

9. Code Examples

9.1 Complete JavaScript Example

class ChatService {
  constructor(jwtToken, roomId) {
    this.jwtToken = jwtToken;
    this.roomId = roomId;
    this.ws = null;
    this.onMessageCallback = null;
    this.onUserOnlineCallback = null;
    this.onUserOfflineCallback = null;
  }

  connect() {
    const url = `wss://api.qsocial.net/ws/chat/${this.roomId}/?token=${this.jwtToken}`;
    this.ws = new WebSocket(url);

    this.ws.onopen = () => {
      console.log('Connected to chat');
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.handleMessage(data);
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };

    this.ws.onclose = (event) => {
      console.log('Connection closed:', event.code, event.reason);
      // Implement reconnection logic here
    };
  }

  handleMessage(data) {
    switch (data.type) {
      case 'chat_message':
        if (this.onMessageCallback) {
          this.onMessageCallback(data);
        }
        break;
      case 'user_online':
        if (this.onUserOnlineCallback) {
          this.onUserOnlineCallback(data);
        }
        break;
      case 'user_offline':
        if (this.onUserOfflineCallback) {
          this.onUserOfflineCallback(data);
        }
        break;
      case 'user_typing':
        if (this.onUserTypingCallback) {
          this.onUserTypingCallback(data);
        }
        break;
      case 'message_read':
        if (this.onMessageReadCallback) {
          this.onMessageReadCallback(data);
        }
        break;
      case 'error':
        if (this.onErrorCallback) {
          this.onErrorCallback(data);
        }
        break;
    }
  }
  
  sendReadReceipt(messageId) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'read_receipt',
        message_id: messageId
      }));
    }
  }

  sendMessage(content) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'chat_message',
        content: content
      }));
    }
  }

  sendTyping() {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'typing'
      }));
    }
  }

  disconnect() {
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
  }
}

// Usage
const chat = new ChatService('your_jwt_token', 123);
chat.onMessageCallback = (data) => {
  console.log('New message:', data);
  // Update UI
};
chat.connect();

Best Practices

Security Considerations


Chat WebSocket Documentation
Last Updated: September 12, 2026
API Version: 1.0