H2V Messenger API
Version 1.1.0 · Updated March 2026
Overview
| Property | Value |
|---|---|
| Base URL | https://web.h2von.com |
| WebSocket URL | wss://web.h2von.com/ws |
| Content-Type | application/json (except file upload) |
| Auth scheme | Session cookie (HttpOnly) |
| Static files | GET /uploads/<filename> |
Authentication Flow
H2V uses email OTP (one-time password) authentication — no passwords required.
Step 1 — Request OTP
POST /api/auth/send-otp
{
"email": "user@example.com"
}
A 6-digit code is sent to the user's email (valid for 10 minutes).
Step 2 — Verify OTP
POST /api/auth/verify-otp
{
"email": "user@example.com",
"code": "123456"
}
If the user exists, a session is created and an auth cookie is set. If the user is new, the server responds with NICKNAME_REQUIRED — call again with nickname:
POST /api/auth/verify-otp
{
"email": "user@example.com",
"code": "123456",
"nickname": "john.doe"
}
Nicknames: 5–32 chars, starts with a letter,
[a-zA-Z0-9.] only.Rate Limits
| Route group | Limit | Window |
|---|---|---|
/api/auth/* | 20 requests | 15 minutes |
All other /api/* | 300 requests | 1 minute |
Exceeding the limit returns HTTP 429 with code RATE_LIMIT.
Response Format
// Success
{ "success": true, "data": { ... } }
// Error
{ "success": false, "code": "ERROR_CODE", "message": "Human-readable" }
// Validation error
{ "success": false, "code": "VALIDATION_ERROR", "errors": { ... } }
Health Endpoints
GET
/health
No auth
Basic liveness check.
GET
/api/health
No auth
Full health check (database + cache status).
Auth Endpoints
POST
/api/auth/send-otp
No auth
Send a 6-digit OTP to the user's email. Rate limited to once per 60 seconds per email.
POST
/api/auth/verify-otp
No auth
Verify OTP, create session. For new users, include
nickname.
POST
/api/auth/logout
Session
End the current session and clear the auth cookie.
GET
/api/auth/sessions
Session
List all active sessions for the current user (device, location, last active).
DELETE
/api/auth/sessions/:id
Session
Terminate a specific session by ID.
DELETE
/api/auth/sessions
Session
Terminate all sessions except the current one.
User Endpoints
GET
/api/users/me
Session
Get the current user's full profile (includes
email).
PATCH
/api/users/me
Session
Update profile. Fields:
nickname, firstName, lastName, avatar, bio (max 70 chars).
DELETE
/api/users/me
Session
Permanently delete account and all associated data.
GET
/api/users/search?q=<query>
Session
Search users by nickname (case-insensitive, up to 20 results).
GET
/api/users/:id
Session
Get any user's public profile.
POST
/api/users/:id/block
Session
Block a user.
DELETE
/api/users/:id/block
Session
Unblock a user.
Settings
GET
/api/users/me/settings
Session
Get the current user's settings (synced across devices).
PUT
/api/users/me/settings
Session
Update settings (partial merge). Available fields:
| Field | Type | Values |
|---|---|---|
notifSound | boolean | Play sound on new messages |
notifDesktop | boolean | Show push notifications |
sendByEnter | boolean | Enter = send |
fontSize | string | small / medium / large |
showOnlineStatus | boolean | Visible to other users |
showReadReceipts | boolean | Send read checkmarks |
mediaAutoDownload | boolean | Auto-download media |
chatWallpaper | string | default / dark / dots / gradient |
locale | string | en / ru |
Chat Endpoints
GET
/api/chats
Session
List chats (sorted by last activity). Params:
cursor, limit (1–100, default 30).
POST
/api/chats/direct
Session
Create or find a direct chat. Body:
{ "targetUserId": "..." }
POST
/api/chats/group
Session
Create a group chat. Body:
{ "name": "...", "memberIds": [...] }
POST
/api/chats/secret
Session
Create an E2E encrypted chat. Body:
{ "targetUserId": "..." }
PATCH
/api/chats/:id
Session
Update chat name/avatar (OWNER or ADMIN).
POST
/api/chats/:id/members
Session
Add members. Body:
{ "userIds": [...] }
DELETE
/api/chats/:id/members/:userId
Session
Remove a member from a group chat.
DELETE
/api/chats/:id/leave
Session
Leave a chat. For direct chats, removes both users.
GET
/api/chats/:id/shared
Session
Shared media. Params:
type (IMAGE/VIDEO/FILE/AUDIO), cursor, limit.Message Endpoints
GET
/api/chats/:chatId/messages
Session
Message history (cursor pagination, newest first). Params:
cursor, limit (1–100), q (full-text search).
PATCH
/api/messages/:id
Session
Edit your message text. Body:
{ "text": "..." }
DELETE
/api/messages/:id
Session
Delete your message permanently.
POST
/api/messages/:id/read
Session
Mark a message as read.
POST
/api/messages/:id/reactions
Session
Add a reaction. Body:
{ "emoji": "👍" }. Allowed: 👍 ❤️ 😂 😮 😢 🔥
DELETE
/api/messages/:id/reactions/:emoji
Session
Remove your reaction (emoji must be URL-encoded).
Contact Endpoints
GET
/api/contacts
Session
Get the current user's contact list.
POST
/api/contacts/:contactId
Session
Add a user to contacts.
DELETE
/api/contacts/:contactId
Session
Remove from contacts.
File Upload
POST
/api/upload
Session
Upload a file (
multipart/form-data, field: file). Max 20 MB.Supported formats: JPEG, PNG, GIF, WebP, MP4, WebM, MP3, OGG, PDF, TXT, ZIP, DOC, DOCX.
Images are auto-optimized: resized and converted to WebP. Three versions are generated (original, medium 800px, thumbnail 200px).
POST
/api/upload/avatar
Session
Upload an avatar (images only, max 10 MB, resized to 400×400).
Keys (Signal Protocol)
H2V supports end-to-end encrypted "secret chats" using the Signal Protocol. These endpoints manage key exchange.
POST
/api/keys/bundle
Session
Upload your PreKey Bundle (identity key, signed prekey, one-time prekeys).
GET
/api/keys/bundle/:userId
Session
Fetch a user's PreKey Bundle (consumes one OTP prekey).
GET
/api/keys/has-bundle/:userId
Session
Check if a user has a bundle (no keys consumed).
POST
/api/keys/replenish
Session
Add more one-time prekeys when count gets low.
GET
/api/keys/count
Session
Get remaining OTP prekey count.
WebSocket Connection
const ws = new WebSocket('wss://web.h2von.com/ws');
ws.onopen = () => {
ws.send(JSON.stringify({
"event": "auth",
"payload": { "token": accessToken }
}));
};
ws.onmessage = (e) => {
const { event, payload } = JSON.parse(e.data);
if (event === 'auth:ok') { /* ready */ }
};
Send
presence:ping every 25 seconds to stay online and prevent timeout.Client → Server Events
| Event | Payload | Description |
|---|---|---|
auth | { token } | Authenticate after connecting |
message:send | { chatId, text, type?, mediaUrl?, replyToId? } | Send a message |
message:read | { messageId, chatId } | Mark as read |
message:listened | { messageId, chatId } | Mark voice message as listened |
typing:start | { chatId } | Start typing indicator |
typing:stop | { chatId } | Stop typing indicator |
presence:ping | — | Keep-alive (every 25s) |
presence:away | — | User switched away |
presence:back | — | User returned |
Server → Client Events
| Event | Recipient | Description |
|---|---|---|
auth:ok | Connecting client | Auth successful, includes online user list |
message:new | All chat members | New message received |
message:delivered | Sender only | At least one recipient is online |
message:read | All chat members | Message was read |
message:edited | All chat members | Message text was edited |
message:deleted | All chat members | Message was deleted |
message:listened | Sender | Voice message was listened to |
chat:new | Recipient | New chat appeared (first message) |
chat:deleted | All former members | Chat was removed |
chat:updated | All members | Chat metadata changed |
reaction:added | All chat members | Reaction added to message |
reaction:removed | All chat members | Reaction removed |
typing:started | All except sender | User is typing |
typing:stopped | All except sender | User stopped typing |
user:online | Relevant users | User came online |
user:offline | Relevant users | User went offline |
user:updated | Shared chat users | Profile updated |
presence:snapshot | New client | List of online users on connect |
error | Requesting client | Server could not process event |
Error Codes
| Code | HTTP | Description |
|---|---|---|
OTP_TOO_SOON | 429 | Resend requested < 60s after previous |
EMAIL_SEND_FAILED | 502 | Email delivery failed |
OTP_EXPIRED | 400 | Code expired (10 min TTL) |
INVALID_CODE | 400 | Wrong OTP code |
OTP_MAX_ATTEMPTS | 429 | 5 wrong attempts — code invalidated |
DISPOSABLE_EMAIL | 422 | Temporary email blocked |
NICKNAME_REQUIRED | 422 | New user must provide nickname |
NICKNAME_TAKEN | 409 | Nickname already registered |
RATE_LIMIT | 429 | Too many requests |
VALIDATION_ERROR | 422 | Invalid request data |
Quick Reference
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /health | — | Liveness |
POST | /api/auth/send-otp | — | Send OTP |
POST | /api/auth/verify-otp | — | Verify OTP |
POST | /api/auth/logout | Session | Logout |
GET | /api/auth/sessions | Session | List sessions |
DELETE | /api/auth/sessions/:id | Session | Terminate session |
GET | /api/users/me | Session | My profile |
PATCH | /api/users/me | Session | Update profile |
DELETE | /api/users/me | Session | Delete account |
GET | /api/users/search?q= | Session | Search users |
GET | /api/users/me/settings | Session | Get settings |
PUT | /api/users/me/settings | Session | Update settings |
GET | /api/chats | Session | Chat list |
POST | /api/chats/direct | Session | Create direct chat |
POST | /api/chats/group | Session | Create group |
POST | /api/chats/secret | Session | Create E2E chat |
GET | /api/chats/:chatId/messages | Session | Message history |
POST | /api/upload | Session | Upload file |
POST | /api/upload/avatar | Session | Upload avatar |