Complete API documentation for HushNet Backend.
- Authentication
- Root Endpoints
- User Endpoints
- Device Endpoints
- Session Endpoints
- Chat Endpoints
- Message Endpoints
- WebSocket Endpoints
- Error Responses
HushNet uses Ed25519 signature-based authentication instead of JWT. Each authenticated request must include the following headers:
| Header | Description | Format |
|---|---|---|
X-Identity-Key |
Base64-encoded Ed25519 public key (32 bytes) | base64(public_key) |
X-Signature |
Base64-encoded signature (64 bytes) | base64(signature) |
X-Timestamp |
Unix timestamp (seconds) | 1698765432 |
- Client generates a current Unix timestamp
- Client signs the timestamp with their Ed25519 private key
- Client sends request with headers:
X-Identity-Key: Device's identity public keyX-Signature: Signature of the timestampX-Timestamp: The timestamp that was signed
- Server verifies:
- Timestamp is within 30 seconds window (anti-replay)
- Signature is valid for the given public key
- Device exists with that identity key
import { sign } from '@noble/ed25519';
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = await sign(
new TextEncoder().encode(timestamp),
privateKey
);
const headers = {
'X-Identity-Key': base64Encode(publicKey),
'X-Signature': base64Encode(signature),
'X-Timestamp': timestamp,
'Content-Type': 'application/json'
};| Status | Error | Description |
|---|---|---|
401 |
Missing X-Identity-Key | Header not provided |
401 |
Missing X-Signature | Header not provided |
401 |
Missing X-Timestamp | Header not provided |
401 |
Expired timestamp | Timestamp outside 30s window |
401 |
Signature mismatch | Invalid signature |
401 |
Unknown device | Device not registered |
400 |
Bad signature b64 | Invalid base64 encoding |
400 |
Signature must be 64 bytes | Wrong signature length |
400 |
Bad pubkey b64 | Invalid base64 encoding |
400 |
Bad pubkey length | Public key not 32 bytes |
400 |
Bad pubkey | Invalid Ed25519 public key |
Health check endpoint.
Authentication: Not required
Response: 200 OK
{
"message": "Hello from HushNet Backend",
"version": "0.1.0",
"status": "healthy"
}List all users.
Authentication: Not required
Response: 200 OK
[
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"username": "alice",
"created_at": "2025-11-02T10:30:00Z"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"username": "bob",
"created_at": "2025-11-02T11:00:00Z"
}
]Create a new user.
Authentication: Not required
Request Body:
{
"username": "alice"
}Response: 201 Created
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"username": "alice",
"created_at": "2025-11-02T10:30:00Z"
}Errors:
400 Bad Request: Invalid username409 Conflict: Username already exists
Get user by ID.
Authentication: Required
Parameters:
id(UUID): User ID
Response: 200 OK
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"username": "alice",
"created_at": "2025-11-02T10:30:00Z"
}Errors:
401 Unauthorized: Invalid authentication404 Not Found: User does not exist
Authenticate a user (returns user info for verification).
Authentication: Not required
Request Body:
{
"username": "alice",
"identity_pubkey": "base64_encoded_public_key"
}Response: 200 OK
{
"user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"device_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"username": "alice"
}Errors:
401 Unauthorized: Invalid credentials404 Not Found: User or device not found
List all devices for a user.
Authentication: Required
Parameters:
user_id(UUID): User ID
Response: 200 OK
[
{
"id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"identity_pubkey": "base64_encoded_key",
"prekey_pubkey": "base64_encoded_key",
"signed_prekey_pub": "base64_encoded_key",
"signed_prekey_sig": "base64_encoded_sig",
"one_time_prekeys": ["key1", "key2", "key3"],
"device_label": "iPhone 15 Pro",
"push_token": "apns_token_here",
"last_seen": "2025-11-02T14:30:00Z",
"created_at": "2025-11-02T10:30:00Z"
}
]Register a new device.
Authentication: Required
Request Body:
{
"user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"identity_pubkey": "base64_encoded_identity_key",
"prekey_pubkey": "base64_encoded_prekey",
"signed_prekey_pub": "base64_encoded_signed_prekey_pub",
"signed_prekey_sig": "base64_encoded_signed_prekey_signature",
"one_time_prekeys": [
"base64_encoded_otpk_1",
"base64_encoded_otpk_2",
"base64_encoded_otpk_3"
],
"device_label": "iPhone 15 Pro",
"push_token": "apns_or_fcm_token"
}Response: 201 Created
{
"id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"identity_pubkey": "base64_encoded_identity_key",
"device_label": "iPhone 15 Pro",
"created_at": "2025-11-02T10:30:00Z"
}Errors:
400 Bad Request: Invalid device data401 Unauthorized: Invalid authentication409 Conflict: Device already exists
Get public keys for all devices of a user (for key exchange).
Authentication: Required
Parameters:
username(string): Username
Response: 200 OK
{
"user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"username": "alice",
"devices": [
{
"device_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"identity_pubkey": "base64_encoded_key",
"signed_prekey_pub": "base64_encoded_key",
"signed_prekey_sig": "base64_encoded_sig",
"one_time_prekey": "base64_encoded_otpk"
}
]
}Note: One-time prekeys are consumed after retrieval.
Initiate an X3DH session with another device.
Authentication: Required
Request Body:
{
"sender_device_id": "your_device_uuid",
"recipient_device_id": "recipient_device_uuid",
"ephemeral_pubkey": "base64_encoded_ephemeral_key",
"sender_prekey_pub": "base64_encoded_sender_prekey",
"otpk_used": "base64_encoded_one_time_prekey_used",
"ciphertext": "base64_encoded_initial_message"
}Response: 201 Created
{
"session_id": "session-uuid",
"status": "pending",
"created_at": "2025-11-02T10:30:00Z"
}Get pending session requests for the authenticated device.
Authentication: Required
Query Parameters:
device_id(UUID): Device ID
Response: 200 OK
[
{
"id": "session-uuid",
"sender_device_id": "sender-device-uuid",
"recipient_device_id": "your-device-uuid",
"ephemeral_pubkey": "base64_encoded_key",
"sender_prekey_pub": "base64_encoded_key",
"otpk_used": "base64_encoded_key",
"ciphertext": "base64_encoded_message",
"state": "initiated",
"created_at": "2025-11-02T10:30:00Z"
}
]Confirm and complete a session.
Authentication: Required
Request Body:
{
"pending_session_id": "session-uuid",
"chat_id": "chat-uuid"
}Response: 200 OK
{
"session_id": "confirmed-session-uuid",
"status": "completed",
"chat_id": "chat-uuid"
}Get all chats for the authenticated user.
Authentication: Required
Query Parameters:
user_id(UUID): User ID
Response: 200 OK
[
{
"id": "chat-uuid",
"chat_type": "direct",
"user_a": "user-uuid-1",
"user_b": "user-uuid-2",
"name": null,
"last_message_id": "message-uuid",
"created_at": "2025-11-02T10:00:00Z",
"updated_at": "2025-11-02T14:30:00Z"
},
{
"id": "chat-uuid-2",
"chat_type": "group",
"name": "Project Team",
"owner_id": "user-uuid-1",
"last_message_id": "message-uuid-2",
"created_at": "2025-11-01T09:00:00Z",
"updated_at": "2025-11-02T15:00:00Z"
}
]Get all device IDs participating in a chat.
Authentication: Required
Parameters:
chat_id(UUID): Chat ID
Response: 200 OK
{
"chat_id": "chat-uuid",
"devices": [
"device-uuid-1",
"device-uuid-2",
"device-uuid-3"
]
}Send an encrypted message.
Authentication: Required
Request Body:
{
"chat_id": "chat-uuid",
"to_device_id": "recipient-device-uuid",
"header": {
"dh_pubkey": "base64_encoded_ratchet_key",
"pn": 0,
"n": 1
},
"ciphertext": "base64_encoded_encrypted_content"
}Response: 201 Created
{
"message_id": "message-uuid",
"logical_msg_id": "logical-uuid",
"created_at": "2025-11-02T14:30:00Z"
}Get pending messages for a device.
Authentication: Required
Parameters:
device_id(UUID): Device ID
Response: 200 OK
[
{
"id": "message-uuid",
"logical_msg_id": "logical-uuid",
"chat_id": "chat-uuid",
"from_user_id": "sender-user-uuid",
"from_device_id": "sender-device-uuid",
"to_user_id": "your-user-uuid",
"to_device_id": "your-device-uuid",
"header": {
"dh_pubkey": "base64_key",
"pn": 0,
"n": 1
},
"ciphertext": "base64_encrypted_content",
"delivered_at": null,
"read_at": null,
"created_at": "2025-11-02T14:30:00Z"
}
]Establish a WebSocket connection for real-time events.
Authentication: Query parameter user_id required
Connection:
const ws = new WebSocket('ws://127.0.0.1:8080/ws?user_id=<user-uuid>');{
"type": "message",
"chat_id": "chat-uuid",
"user_id": "recipient-user-uuid"
}Action: Fetch pending messages for the chat.
{
"type": "session",
"user_id": "affected-user-uuid",
"sender_device_id": "sender-device-uuid",
"receiver_device_id": "receiver-device-uuid"
}Action: Fetch pending sessions.
{
"type": "device",
"user_id": "user-uuid"
}Action: Refresh device list for the user.
{
"error": "Error message description",
"code": "ERROR_CODE",
"status": 400
}| Status Code | Description |
|---|---|
200 OK |
Request successful |
201 Created |
Resource created successfully |
400 Bad Request |
Invalid request data |
401 Unauthorized |
Authentication failed |
403 Forbidden |
Access denied |
404 Not Found |
Resource not found |
409 Conflict |
Resource already exists |
422 Unprocessable Entity |
Validation failed |
500 Internal Server Error |
Server error |
Currently, there is no rate limiting implemented. For production deployments, consider implementing rate limiting at the application or infrastructure level.
- Authentication requests: 5 per minute per IP
- Message sending: 60 per minute per device
- WebSocket connections: 5 concurrent per user
- API requests: 100 per minute per device
- Always verify signatures on the client side before sending
- Handle timestamp synchronization - ensure client clock is accurate
- Implement exponential backoff for failed requests
- Cache device keys to reduce API calls
- Use WebSocket for real-time updates instead of polling
- Validate all cryptographic materials before use
- Implement proper error handling for all API calls