WebSocket API
Real-time bidirectional communication for message delivery, presence updates, and live notifications.
Overview
The Zentalk WebSocket API provides persistent connections for real-time communication. Unlike the REST API which requires polling, WebSocket connections enable instant message delivery and presence updates with minimal latency.
Key Features
| Feature | Description |
|---|---|
| Persistent Connection | Single long-lived connection for all real-time events |
| Bidirectional | Both client and server can initiate messages |
| Low Latency | Sub-100ms message delivery typical |
| Automatic Reconnection | Client SDK handles reconnection with backoff |
| Multiplexed | Single connection handles all conversations |
When to Use WebSocket vs REST
| Use Case | Recommended |
|---|---|
| Receiving messages in real-time | WebSocket |
| Sending messages | WebSocket |
| Fetching message history | REST API |
| Uploading media | REST API |
| Managing keys | REST API |
| Presence and typing indicators | WebSocket |
Connection Lifecycle
Connection URL
wss://relay.zentalk.network/ws/v1| Parameter | Required | Description |
|---|---|---|
token | Yes | JWT authentication token |
device_id | Yes | Unique device identifier |
protocol_version | No | Protocol version (default: 1) |
resume_token | No | Token to resume previous session |
Example connection URL:
wss://relay.zentalk.network/ws/v1?token=<jwt>&device_id=<uuid>&protocol_version=1Connection States
┌──────────────┐
│ DISCONNECTED │
└──────┬───────┘
│ connect()
▼
┌──────────────┐
│ CONNECTING │───────────────────────────────┐
└──────┬───────┘ │
│ WebSocket open │ timeout/error
▼ ▼
┌──────────────┐ ┌──────────────┐
│AUTHENTICATING│───────────────────────→│ FAILED │
└──────┬───────┘ auth failed └──────────────┘
│ auth success ▲
▼ │
┌──────────────┐ │
│ CONNECTED │───────────────────────────────┘
└──────┬───────┘ connection lost
│ close()
▼
┌──────────────┐
│ CLOSING │
└──────┬───────┘
│ closed
▼
┌──────────────┐
│ DISCONNECTED │
└──────────────┘| State | Description | Duration |
|---|---|---|
| DISCONNECTED | No active connection | Until connect() called |
| CONNECTING | TCP/TLS handshake in progress | Typically < 1 second |
| AUTHENTICATING | Challenge-response authentication | < 5 seconds |
| CONNECTED | Fully authenticated and ready | Indefinite |
| CLOSING | Graceful shutdown in progress | < 2 seconds |
| FAILED | Connection attempt failed | Until retry |
Authentication Handshake
After WebSocket connection is established, authentication occurs:
Client Server
│ │
├──────── WebSocket Connect ─────────────→│
│ │
│←─────── AUTH_CHALLENGE ─────────────────┤
│ { challenge: <random_32_bytes>, │
│ server_time: <timestamp> } │
│ │
├──────── AUTH_RESPONSE ─────────────────→│
│ { signature: Sign(IK, challenge),
│ identity_key: <public_key>, │
│ device_id: <uuid>, │
│ timestamp: <client_time> } │
│ │
│←─────── AUTH_SUCCESS ───────────────────┤
│ { session_token: <token>, │
│ expires_at: <timestamp>, │
│ server_time: <timestamp> } │
│ │
│ Connection Ready │
└─────────────────────────────────────────┘Heartbeat Mechanism
The connection uses ping-pong frames for keepalive:
| Parameter | Value |
|---|---|
| Ping interval | 30 seconds |
| Pong timeout | 10 seconds |
| Max missed pings | 3 |
| Total timeout | 120 seconds |
Client Server
│ │
│←─────── PING ───────────────────────────┤
│ { timestamp: <server_time> } │
│ │
├──────── PONG ─────────────────────────→ │
│ { timestamp: <server_time>, │
│ client_time: <client_time> } │
│ │
│ (repeated every 30 seconds) │
└─────────────────────────────────────────┘Latency Measurement:
The server calculates round-trip time from ping-pong exchange. Clients can use this for connection quality indicators:
| RTT | Quality |
|---|---|
| < 100ms | Excellent |
| 100-300ms | Good |
| 300-1000ms | Fair |
| > 1000ms | Poor |
Authentication Protocol
Challenge-Response Flow
Authentication uses Ed25519 signatures to prove identity key ownership without transmitting private keys.
Step 1: Server Challenge
{
"type": "AUTH_CHALLENGE",
"challenge": "<base64_32_random_bytes>",
"server_time": 1704067200000,
"nonce": "<base64_16_bytes>"
}Step 2: Client Response
signed_data = challenge || server_time || device_id || client_time
{
"type": "AUTH_RESPONSE",
"identity_key": "<base64_ed25519_public_key>",
"device_id": "<uuid>",
"signature": "<base64_ed25519_signature>",
"client_time": 1704067200500
}Step 3: Server Verification
1. Verify signature using identity_key
2. Check challenge matches issued challenge
3. Validate timestamp within 5 minute window
4. Look up identity_key in user registry
5. Issue session token if validJWT Token Format
Session tokens are JWTs with the following structure:
Header:
{
"alg": "EdDSA",
"typ": "JWT"
}Claims:
| Claim | Type | Description |
|---|---|---|
sub | string | Wallet address (0x…) |
iat | number | Issued at (Unix timestamp) |
exp | number | Expiration (Unix timestamp) |
device_id | string | Device UUID |
session_id | string | Unique session identifier |
capabilities | array | Granted capabilities |
Example Claims:
{
"sub": "0x1234567890abcdef1234567890abcdef12345678",
"iat": 1704067200,
"exp": 1704153600,
"device_id": "550e8400-e29b-41d4-a716-446655440000",
"session_id": "sess_abc123def456",
"capabilities": ["send_message", "receive_message", "presence"]
}Token Refresh
Tokens expire after 24 hours. Refresh must occur before expiration:
| Parameter | Value |
|---|---|
| Token lifetime | 24 hours |
| Refresh window | Last 2 hours before expiry |
| Grace period | 5 minutes after expiry |
| Max refresh attempts | 3 per hour |
Client Server
│ │
├──────── TOKEN_REFRESH ─────────────────→│
│ { session_token: <old_token>, │
│ signature: Sign(IK, token) } │
│ │
│←─────── TOKEN_REFRESHED ────────────────┤
│ { session_token: <new_token>, │
│ expires_at: <timestamp> } │
└─────────────────────────────────────────┘Multi-Device Connection Handling
Each device maintains an independent WebSocket connection. The server manages device multiplexing:
| Behavior | Description |
|---|---|
| Connection limit | Maximum 5 concurrent devices per user |
| Message fanout | Messages delivered to all connected devices |
| Presence aggregation | User shows online if any device connected |
| Device priority | Most recently active device for notifications |
Device Registration:
| Field | Description |
|---|---|
device_id | UUID, generated once per device install |
device_name | User-friendly name (e.g., “iPhone 15”) |
device_type | mobile, desktop, tablet, web |
push_token | FCM/APNs token for offline notifications |
Message Types
Frame Format
All WebSocket messages use JSON text frames with a consistent envelope:
{
"type": "<MESSAGE_TYPE>",
"id": "<unique_message_id>",
"timestamp": <unix_ms>,
"payload": { ... }
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Message type identifier |
id | string | Yes | Unique message ID (UUID) |
timestamp | number | Yes | Unix timestamp in milliseconds |
payload | object | Yes | Type-specific payload |
Inbound Message Types (Server to Client)
| Type | Description | Payload Fields |
|---|---|---|
AUTH_CHALLENGE | Authentication challenge | challenge, server_time, nonce |
AUTH_SUCCESS | Authentication succeeded | session_token, expires_at, server_time |
AUTH_FAILED | Authentication failed | error_code, message |
MESSAGE_NEW | New encrypted message | from, ciphertext, nonce, timestamp |
MESSAGE_ACK | Server received message | message_id, server_timestamp |
MESSAGE_DELIVERED | Message delivered to recipient | message_id, device_id, timestamp |
MESSAGE_READ | Message read by recipient | message_id, timestamp |
TYPING_START | Peer started typing | conversation_id, user_id |
TYPING_STOP | Peer stopped typing | conversation_id, user_id |
PRESENCE_UPDATE | User presence changed | user_id, status, last_seen |
KEY_CHANGE | Peer’s identity key changed | user_id, new_key, reason |
PING | Keepalive ping | timestamp |
ERROR | Server error | error_code, message, details |
SYNC_REQUIRED | Client should fetch updates | reason, since_timestamp |
Outbound Message Types (Client to Server)
| Type | Description | Payload Fields |
|---|---|---|
AUTH_RESPONSE | Authentication response | identity_key, signature, device_id, client_time |
MESSAGE_SEND | Send encrypted message | to, ciphertext, nonce, message_type |
MESSAGE_RECEIVED | Confirm message receipt | message_id |
MESSAGE_READ | Mark message as read | message_id |
TYPING_START | Started typing | conversation_id |
TYPING_STOP | Stopped typing | conversation_id |
PRESENCE_SET | Set own presence | status |
SUBSCRIBE | Subscribe to updates | topics |
UNSUBSCRIBE | Unsubscribe from updates | topics |
PONG | Keepalive response | timestamp, client_time |
TOKEN_REFRESH | Request token refresh | session_token, signature |
Binary vs Text Frames
| Frame Type | Usage |
|---|---|
| Text | All control messages, presence, typing, ACKs |
| Binary | Large encrypted payloads (> 16KB) |
Binary frame format for large messages:
┌────────────────────────────────────────┐
│ Message Type (1 byte) │
├────────────────────────────────────────┤
│ Message ID (16 bytes, UUID) │
├────────────────────────────────────────┤
│ Timestamp (8 bytes, big-endian) │
├────────────────────────────────────────┤
│ Payload Length (4 bytes, big-endian) │
├────────────────────────────────────────┤
│ Payload (variable) │
└────────────────────────────────────────┘Real-Time Events
New Message Notification
When a new message arrives for the client:
{
"type": "MESSAGE_NEW",
"id": "msg_abc123def456",
"timestamp": 1704067200000,
"payload": {
"from": "0x9876543210fedcba9876543210fedcba98765432",
"conversation_id": "conv_xyz789",
"ciphertext": "<base64_encrypted_message>",
"nonce": "<base64_12_bytes>",
"message_type": "text",
"x3dh_header": {
"identity_key": "<base64_key>",
"ephemeral_key": "<base64_key>",
"one_time_key_id": 42
}
}
}| Field | Description |
|---|---|
from | Sender’s wallet address |
conversation_id | Conversation identifier |
ciphertext | E2E encrypted message content |
nonce | Encryption nonce |
message_type | text, image, file, voice, location |
x3dh_header | Present only for session-initiating messages |
Typing Indicators
Typing indicators show when a peer is composing a message:
Start Typing (outbound):
{
"type": "TYPING_START",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"conversation_id": "conv_xyz789"
}
}Typing Update (inbound):
{
"type": "TYPING_START",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"conversation_id": "conv_xyz789",
"user_id": "0x9876543210fedcba9876543210fedcba98765432"
}
}| Behavior | Value |
|---|---|
| Typing timeout | 5 seconds (auto-stop if no update) |
| Throttle interval | 3 seconds between updates |
| Display duration | Until TYPING_STOP or timeout |
Presence Updates
Presence indicates user online/offline status:
Status Values:
| Status | Description |
|---|---|
online | User has active connection |
away | Connected but inactive > 5 minutes |
offline | No active connections |
invisible | Online but appearing offline (privacy) |
Presence Event:
{
"type": "PRESENCE_UPDATE",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"user_id": "0x9876543210fedcba9876543210fedcba98765432",
"status": "online",
"last_seen": 1704067200000,
"device_count": 2
}
}Presence Privacy:
| Setting | Behavior |
|---|---|
| Share presence | Contacts see real status |
| Hide presence | Always appear offline |
| Last seen | Show/hide last active timestamp |
| Typing indicators | Can be disabled per-contact |
Read Receipts
Read receipts confirm message viewing:
Send Read Receipt:
{
"type": "MESSAGE_READ",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"message_id": "msg_abc123def456"
}
}Receive Read Receipt:
{
"type": "MESSAGE_READ",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"message_id": "msg_abc123def456",
"read_by": "0x9876543210fedcba9876543210fedcba98765432",
"timestamp": 1704067205000
}
}| Privacy Option | Behavior |
|---|---|
| Send read receipts | ON: Sender sees when you read |
| Send read receipts | OFF: No read receipts sent |
Key Change Notifications
When a contact’s identity key changes:
{
"type": "KEY_CHANGE",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"user_id": "0x9876543210fedcba9876543210fedcba98765432",
"old_key_fingerprint": "37291 84756 19283...",
"new_key_fingerprint": "94857 29183 74829...",
"reason": "device_added",
"verified": false
}
}| Reason | Description | Risk Level |
|---|---|---|
device_added | New device registered | Low |
key_rotation | Scheduled key rotation | Low |
reinstall | App reinstalled | Medium |
unknown | Reason not provided | High |
Message Delivery Flow
Send Message Flow
Client A Server Client B
│ │ │
├── MESSAGE_SEND ───────→│ │
│ (encrypted payload) │ │
│ │ │
│←── MESSAGE_ACK ────────┤ (queued for B) │
│ (server_timestamp) │ │
│ │ │
│ ├──── MESSAGE_NEW ──────→│
│ │ │
│ │←── MESSAGE_RECEIVED ───┤
│ │ │
│←── MESSAGE_DELIVERED ──┤ │
│ │ │
│ │←── MESSAGE_READ ───────┤
│ │ │
│←── MESSAGE_READ ───────┤ │
└────────────────────────┴────────────────────────┘Acknowledgment Stages
| Stage | Event | Meaning |
|---|---|---|
| 1 | Sent | Client transmitted to server |
| 2 | ACK | Server received and stored |
| 3 | Delivered | Recipient device received |
| 4 | Read | Recipient viewed message |
Server Acknowledgment
After receiving a message, server responds immediately:
{
"type": "MESSAGE_ACK",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"message_id": "msg_abc123def456",
"server_timestamp": 1704067200100,
"status": "queued",
"recipient_devices": 2
}
}Delivery Confirmation
When recipient receives the message:
{
"type": "MESSAGE_DELIVERED",
"id": "<uuid>",
"timestamp": 1704067200500,
"payload": {
"message_id": "msg_abc123def456",
"device_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": 1704067200500
}
}Offline Message Queuing
When recipient is offline, messages are queued:
| Parameter | Value |
|---|---|
| Max queue size | 1000 messages per device |
| Max retention | 30 days |
| Priority | Newer messages prioritized |
| Overflow behavior | Oldest messages dropped |
Queue Processing on Connect:
1. Client connects and authenticates
2. Server sends SYNC_REQUIRED with pending count
3. Client requests queued messages via REST API
4. Messages delivered in chronological order
5. Push notifications cleared after deliveryQueue Status:
{
"type": "SYNC_REQUIRED",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"reason": "offline_messages",
"pending_count": 47,
"oldest_timestamp": 1704000000000,
"newest_timestamp": 1704067199000
}
}Error Handling
WebSocket Close Codes
| Code | Name | Description | Reconnect? |
|---|---|---|---|
| 1000 | Normal | Clean close by client or server | Optional |
| 1001 | Going Away | Server shutting down | Yes |
| 1002 | Protocol Error | Invalid frame received | Yes |
| 1003 | Unsupported | Received unsupported data type | Yes |
| 1006 | Abnormal | Connection lost unexpectedly | Yes |
| 1008 | Policy Violation | Message violated policy | No |
| 1009 | Too Large | Message exceeded size limit | No |
| 1011 | Server Error | Unexpected server error | Yes |
| 4000 | Auth Failed | Authentication failed | No |
| 4001 | Token Expired | Session token expired | Refresh token |
| 4002 | Rate Limited | Too many requests | After cooldown |
| 4003 | Banned | Account suspended | No |
| 4004 | Device Limit | Too many devices | Remove device |
Error Message Format
{
"type": "ERROR",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"error_code": "RATE_LIMITED",
"message": "Too many messages sent",
"details": {
"limit": 100,
"window": 60,
"retry_after": 45
},
"request_id": "req_xyz789"
}
}Error Codes
| Error Code | HTTP Equiv | Description |
|---|---|---|
INVALID_MESSAGE | 400 | Malformed message format |
UNAUTHORIZED | 401 | Invalid or missing authentication |
FORBIDDEN | 403 | Action not permitted |
NOT_FOUND | 404 | Resource not found |
RATE_LIMITED | 429 | Rate limit exceeded |
INTERNAL_ERROR | 500 | Server error |
SERVICE_UNAVAILABLE | 503 | Server overloaded |
Reconnection Strategy
Implement exponential backoff for reconnection:
attempt = 0
max_attempts = 10
base_delay = 1000ms
while not connected and attempt < max_attempts:
delay = min(30000, base_delay * (2 ^ attempt))
jitter = random(0, delay * 0.1)
wait(delay + jitter)
try:
connect()
attempt = 0
catch:
attempt = attempt + 1| Attempt | Delay | With Jitter (max) |
|---|---|---|
| 1 | 1s | 1.1s |
| 2 | 2s | 2.2s |
| 3 | 4s | 4.4s |
| 4 | 8s | 8.8s |
| 5 | 16s | 17.6s |
| 6+ | 30s | 33s |
Message Retry on Disconnect
When connection drops during message send:
1. MESSAGE_SEND transmitted
2. Connection lost before MESSAGE_ACK
3. Client stores message in pending queue
4. Reconnection successful
5. Resend all pending messages with original IDs
6. Server deduplicates by message ID
7. Client receives MESSAGE_ACK for eachPending Message Storage:
| Field | Description |
|---|---|
message_id | Original message UUID |
payload | Complete message payload |
first_attempt | Timestamp of first send attempt |
retry_count | Number of retry attempts |
max_retries | Maximum attempts (default: 5) |
Server-Side Error Handling
When server encounters an error processing a request:
{
"type": "ERROR",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"error_code": "RECIPIENT_NOT_FOUND",
"message": "Recipient address not registered",
"request_id": "req_abc123",
"original_type": "MESSAGE_SEND"
}
}Handling Specific Errors:
| Error | Client Action |
|---|---|
RECIPIENT_NOT_FOUND | Show error to user, don’t retry |
RATE_LIMITED | Wait retry_after seconds |
INVALID_MESSAGE | Log error, don’t retry |
INTERNAL_ERROR | Retry with backoff |
KEY_MISMATCH | Fetch new key bundle, retry |
Rate Limiting
Connection Limits
| Limit | Value | Scope |
|---|---|---|
| Max connections per user | 5 | All devices combined |
| Max connections per IP | 10 | Shared networks |
| Connection rate | 10/minute | Per device |
| Auth attempts | 5/minute | Per IP |
Message Rate Limits
| Operation | Limit | Window | Burst |
|---|---|---|---|
| Send message | 60 | 60 seconds | 10 |
| Typing indicator | 1 | 3 seconds | 1 |
| Presence update | 1 | 60 seconds | 1 |
| Read receipt | 100 | 60 seconds | 20 |
| Subscribe | 10 | 60 seconds | 5 |
Typing Indicator Throttling
To prevent abuse, typing indicators are throttled:
Client Server
│ │
├── TYPING_START ────────────────────────→│ Accepted
│ (t = 0s) │
│ │
├── TYPING_START ────────────────────────→│ Ignored (too soon)
│ (t = 1s) │
│ │
├── TYPING_START ────────────────────────→│ Accepted
│ (t = 3s) │
│ │
│ (user stops typing) │
│ │
├── TYPING_STOP ─────────────────────────→│ Accepted
└─────────────────────────────────────────┘Backpressure Handling
When rate limited, the server sends:
{
"type": "ERROR",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"error_code": "RATE_LIMITED",
"message": "Message rate limit exceeded",
"details": {
"limit": 60,
"window_seconds": 60,
"current_count": 60,
"retry_after": 15,
"reset_at": 1704067215000
}
}
}Client Backpressure Strategy:
if rate_limited:
pause_sending()
queue_outgoing_messages()
wait(retry_after)
resume_sending()
drain_queue_with_pacing()| Queue Behavior | Description |
|---|---|
| Max queue size | 100 messages |
| Drain rate | 1 message per 100ms |
| Priority | Newer messages first |
| Overflow | Reject with error to user |
Security Considerations
TLS Requirements
| Requirement | Value |
|---|---|
| Minimum TLS version | TLS 1.3 |
| Certificate validation | Required |
| Certificate pinning | Recommended |
| HSTS | Enforced |
Supported Cipher Suites:
| Priority | Cipher Suite |
|---|---|
| 1 | TLS_AES_256_GCM_SHA384 |
| 2 | TLS_CHACHA20_POLY1305_SHA256 |
| 3 | TLS_AES_128_GCM_SHA256 |
Origin Validation
Server validates WebSocket upgrade requests:
| Header | Validation |
|---|---|
Origin | Must match allowed origins list |
Host | Must be relay.zentalk.network |
Sec-WebSocket-Key | Must be valid base64 |
Sec-WebSocket-Protocol | Must be “zentalk.v1” |
Allowed Origins:
https://app.zentalk.network
https://zentalk.network
capacitor://localhost (mobile apps)Token Expiration Handling
Tokens are validated on every message:
1. Extract token from session
2. Verify signature
3. Check exp claim against server time
4. If expired:
a. Check grace period (5 minutes)
b. If within grace: allow, send TOKEN_EXPIRING warning
c. If past grace: close connection with 4001
5. If valid: process messageToken Expiring Warning:
{
"type": "TOKEN_EXPIRING",
"id": "<uuid>",
"timestamp": 1704067200000,
"payload": {
"expires_at": 1704070800000,
"seconds_remaining": 1800,
"action": "refresh_recommended"
}
}Replay Attack Prevention
Each message includes protections against replay:
| Protection | Implementation |
|---|---|
| Nonce | Unique per message, never reused |
| Timestamp | Must be within 5 minute window |
| Sequence number | Monotonically increasing per session |
| Message ID | Server rejects duplicate IDs |
Server-Side Deduplication:
1. Receive message with ID
2. Check bloom filter for ID
3. If possibly present:
a. Check database for exact match
b. If found: reject as duplicate
4. If not present:
a. Add to bloom filter
b. Store in database with TTL (24 hours)
c. Process messageMessage Integrity
All messages are integrity protected:
message_mac = HMAC-SHA256(session_key, type || id || timestamp || payload)
Client includes message_mac in envelope
Server verifies before processing
Tampering detected = connection closedImplementation Recommendations
Client Implementation
WebSocket Client Pseudocode:
class ZentalkWebSocket:
state = DISCONNECTED
pending_messages = []
reconnect_attempts = 0
function connect():
state = CONNECTING
ws = new WebSocket(connection_url)
ws.onopen = handle_open
ws.onmessage = handle_message
ws.onclose = handle_close
ws.onerror = handle_error
function handle_open():
state = AUTHENTICATING
// Server will send AUTH_CHALLENGE
function handle_message(msg):
data = parse_json(msg)
switch data.type:
case "AUTH_CHALLENGE":
send_auth_response(data.payload)
case "AUTH_SUCCESS":
state = CONNECTED
reconnect_attempts = 0
drain_pending_messages()
case "MESSAGE_NEW":
emit("message", data.payload)
case "PING":
send_pong(data.payload)
// ... handle other types
function handle_close(code):
state = DISCONNECTED
if should_reconnect(code):
schedule_reconnect()
function send(type, payload):
if state != CONNECTED:
pending_messages.push({type, payload})
return
ws.send(json({
type: type,
id: generate_uuid(),
timestamp: now(),
payload: payload
}))Server Implementation Considerations
| Concern | Recommendation |
|---|---|
| Connection limit | Use connection pooling |
| Message ordering | Sequence numbers per session |
| Horizontal scaling | Sticky sessions or pub/sub |
| Presence aggregation | Distributed cache (Redis) |
| Message queue | Persistent store (PostgreSQL) |
Mobile Considerations
| Challenge | Solution |
|---|---|
| Background restrictions | Use push notifications |
| Battery optimization | Reduce ping frequency when backgrounded |
| Network transitions | Implement connection recovery |
| Memory constraints | Limit pending message queue |
Related Documentation
- API Reference - REST API for non-realtime operations
- Protocol Specification - Cryptographic protocol details
- Wire Protocol - Binary message formats
- Multi-Device Support - Device management