V4CALL
| Please Select a Licence from the LICENCE_HEADERS page |
And place at top of your page |
If no Licence is Selected/Appended, Default will be CC0 Default Licence IF there is no Licence placed below this notice!
When you edit this page, you agree to release your contribution under the CC0 Licence LICENCE:
More information about the cc0 licence can be found here: You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. Licence: Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; moral rights retained by the original author(s) and/or performer(s); publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; rights protecting the extraction, dissemination, use and reuse of data in a Work; database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. |
|
LICENCE: When you edit this page, you agree to release your contribution under the MIT Licence LICENCE Copyright <YEAR> <COPYRIGHT HOLDER> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
v4call — Deploy Your Own Server on Ubuntu 24.04 with Docker
From CompleteNoobs
This guide walks through deploying your own v4call server from scratch on a fresh Vultr Ubuntu 24.04 VPS — from first login to a working HTTPS video/audio calling service on your own domain.
v4call is an open-source, decentralised video and audio calling platform that uses Hive blockchain for identity and HBD micropayments. It supports custom Hive-Engine token payments, encrypted direct messages with persistent chat history, voice-only and video calls, and a free-market platform fee system. Fork it, run your own server, keep all your platform fees, join the federation, federation coming (unknown time) kismet.
Source code: https://github.com/CompleteNoobs/v4call
End result: A working v4call server at https://call.yourdomain.com that you can log into with a Hive account.
Contents
- 1 What You Need
- 2 Step 1: Create Your Vultr VPS
- 3 Step 2: Point Your Domain at the VPS
- 4 Step 3: Log into Your VPS
- 5 Step 4: Update the Server
- 6 Step 5: Install Docker
- 7 Step 6: Install Git
- 8 Step 7: Fork and Clone the Code
- 9 Step 8: Configure Your Server (.env file)
- 10 Step 9: Configure Nginx — HTTP Only First
- 11 Step 10: Create Data Directories and Fix Permissions
- 12 Step 11: Build and Start the Server
- 13 Step 12: Get Your SSL Certificate
- 14 Step 13: Enable HTTPS in Nginx
- 15 Step 14: Set Up SSL Auto-Renewal
- 16 Step 15: Test Everything is Working
- 17 Step 16: Set Up Your Call Rates on Hive
- 18 Feature Guide: What Your Server Can Do
- 19 Admin Configuration Reference
- 20 Updating Your Server
- 21 Common Problems and Fixes
- 22 Quick Reference
What You Need
Before starting, make sure you have the following:
- A Vultr account — sign up at vultr.com - please use are Vultr Referral link to help us cover server costs.
- A domain name with DNS access — e.g.
call.yourdomain.com - Two Hive accounts — one for your server identity (receives platform fees), one for escrow (holds caller funds during calls). Create free accounts at signup.hive.io
- Hive Keychain browser extension — for login and payments. Install from hive-keychain.com
- A GitHub account — free at github.com. You will fork the v4call project.
- A terminal — Mac: Terminal app. Windows: PowerShell or PuTTY.
You do not need to know how to code. Every command can be copy-pasted exactly as shown.
Step 1: Create Your Vultr VPS
- Log into my.vultr.com
- Click Deploy New Server
- Choose Cloud Compute — Shared CPU
- Choose a location close to you
- Choose Ubuntu 24.04 LTS x64
- Choose the $6/month plan (1 CPU, 1GB RAM)
- Set Server Hostname to something like
v4call-server - Click Deploy Now
Wait ~60 seconds for it to start. Click the server to find:
- IP Address — looks like
123.456.789.012— write it down - Password — click the eye icon — write it down
Step 2: Point Your Domain at the VPS
Log into your DNS provider and add an A record:
| Field | Value |
|---|---|
| Type | A |
| Name | call (or @ for root domain)
|
| Value | Your VPS IP address |
| TTL | 300 |
DNS takes a few minutes to propagate. Check from your computer later:
nslookup call.yourdomain.com
Must show your VPS IP before Step 12. You can continue with all other steps while waiting.
Step 3: Log into Your VPS
Open a terminal on your computer:
ssh root@YOUR_SERVER_IP
Type yes when asked about the fingerprint, then paste the password from Vultr (right-click to paste in most terminals).
When you see root@v4call-server:~# you are in.
Step 4: Update the Server
apt update && apt upgrade -y
Wait for it to complete (1-2 minutes).
Step 5: Install Docker
Install Docker using the official installer script:
curl -fsSL https://get.docker.com | sh
Verify:
docker --version
Should show Docker version 26.x.x or similar.
Install Docker Compose plugin:
apt install -y docker-compose-plugin
Verify:
docker compose version
Should show Docker Compose version v2.x.x.
Step 6: Install Git
apt install -y git git --version
Step 7: Download and/or Fork and Clone the Code
Clone onto your VPS
cd /opt git clone https://github.com/CompleteNoobs/v4call.git cd v4call
Fork on GitHub - Optional
Forking gives you your own copy of the code that you can customise — change the name, branding, fees — without affecting the original project.
- Go to https://github.com/CompleteNoobs/v4call
- Click the Fork button (top right of the page)
- Select your GitHub account as the destination
- Click Create fork
You now have your own copy at https://github.com/YOURUSERNAME/v4call
Clone onto your VPS
cd /opt git clone https://github.com/YOURUSERNAME/v4call.git cd v4call
List the files to confirm:
ls
You should see: server.js public/ Dockerfile docker-compose.yml nginx/ package.json .env.example
Step 8: Configure Your Server (.env file)
All settings live in a single .env file. Copy the template:
cp .env.example .env nano .env
Fill in your values:
# ── Server Identity ────────────────────────────────────── SERVER_NAME=yourcallapp SERVER_DOMAIN=call.yourdomain.com SERVER_HIVE_ACCOUNT=yourhiveaccount ESCROW_ACCOUNT=yourescrowaccount V4CALL_ESCROW_KEY=5Kxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Secret key to access /admin/ledger and /admin/balance endpoints. # Choose a long random string — treat it like a password. # Example: openssl rand -hex 32 ADMIN_KEY=make-up-a-long-random-string-at-least-20-characters # ── Hive Blockchain ────────────────────────────────────── CHAIN=hive HIVE_API= # ── Platform Fee ───────────────────────────────────────── # Percentage your server takes from each paid call/DM (10 = 10%) # This is the MINIMUM fee — users whose rates post sets a lower # platform fee will be rejected. Users who set a higher fee # get the best price (your server's rate, not their higher number). DEFAULT_PLATFORM_FEE=10 # ── Network ────────────────────────────────────────────── PORT=3000 BIND_HOST=127.0.0.1 # ── Chat Storage ───────────────────────────────────────── # How many days to keep stored DMs before automatic cleanup DM_RETENTION_DAYS=33 # How many days to keep stored room messages before cleanup ROOM_RETENTION_DAYS=33 # How many recent DMs per conversation to show on login (0 = off) DM_PREVIEW_COUNT=1 # ── Call Behaviour (advanced — defaults are fine) ──────── # CALL_COOLDOWN_MS=30000 # MAX_CALL_DURATION_MIN=120 # PAYMENT_VERIFY_RETRIES=3 # PAYMENT_VERIFY_DELAY_MS=5000
Key points:
V4CALL_ESCROW_KEY— must be the active private key for your escrow account. Find it in your Hive wallet → Keys & Permissions → Active. Starts with5K. Never share this.ADMIN_KEY— invent a secret password for accessing admin toolsHIVE_API— leave blank to use all built-in Hive nodes automaticallyDEFAULT_PLATFORM_FEE— your server's minimum platform fee percentage. See Platform Fee System below for how this works.DM_RETENTION_DAYS/ROOM_RETENTION_DAYS— how long chat messages are kept in the database. A cleanup job runs every hour and deletes anything older. Set to0to keep messages indefinitely (not recommended).DM_PREVIEW_COUNT— when a user logs in, this many recent DMs per conversation are loaded into the lobby chat so they can see previews. Set to0to disable previews (users still get an unread count alert).
Save: Ctrl+X → Y → Enter
Step 9: Configure Nginx — HTTP Only First
This step is critical. A very common mistake is putting the HTTPS/SSL config in Nginx before getting the certificate. Nginx tries to load the certificate at startup — if the file doesn't exist yet, Nginx crashes in a restart loop. Certbot then cannot serve the challenge because Nginx is down. Result: no certificate, stuck in a loop.
The fix: always start with HTTP only, get the certificate, then add HTTPS.
Edit the Nginx config:
nano /opt/v4call/nginx/v4call.conf
Delete everything and replace with this HTTP-only config:
server {
listen 80;
server_name call.yourdomain.com www.call.yourdomain.com;
# Certbot challenge path — do not remove this block
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Proxy all other requests to the v4call app
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
}
Replace call.yourdomain.com with your actual domain in both places.
Save: Ctrl+X → Y → Enter
Step 10: Create Data Directories and Fix Permissions
Create the folders Docker uses for persistent data:
mkdir -p /opt/v4call/data/logs \ mkdir -p /opt/v4call/data/certbot/conf \ mkdir -p /opt/v4call/data/certbot/www/.well-known/acme-challenge \ mkdir -p /opt/v4call/data/certbot/logs
Fix permissions — do not skip this.
The v4call app runs inside Docker as user node (UID 1000). On the host, these directories are created by root, so the container cannot write to them. This causes a SQLITE_CANTOPEN error that crashes the app.
Fix the logs directory for the app:
chown -R 1000:1000 /opt/v4call/data/logs
Certbot runs as root so its directories stay root-owned:
chown -R root:root /opt/v4call/data/certbot chmod -R 755 /opt/v4call/data/certbot
Note: The app creates two separate SQLite databases in the logs directory:
v4call-ledger.db— payment records (calls, ring fees, payouts). Only the server writes to this.v4call-chat.db— stored DMs and room messages. Separate from the ledger for security — if a bug in chat storage were exploited, the payment ledger remains untouched.
Step 11: Build and Start the Server
cd /opt/v4call docker compose up -d --build
The first build downloads dependencies and takes 2-4 minutes. Check the status:
docker compose ps
You should see:
NAME STATUS v4call-app Up (healthy) v4call-nginx Up
Check the app started correctly:
docker compose logs app
Look for:
[ledger] SQLite ready: /app/logs/v4call-ledger.db [chat] SQLite ready: /app/logs/v4call-chat.db v4call server running on 0.0.0.0:3000 [config] Server: yourcallapp (call.yourdomain.com) [config] DM retention: 33 days | Room retention: 33 days | DM preview: 1 ✓ Escrow key verified — matches @yourescrowaccount active key ✓ Escrow account @yourescrowaccount balance: 0.000 HBD
If you see SqliteError: unable to open database file — run the chown command from Step 10 again then docker compose restart app.
Test HTTP is working:
curl http://call.yourdomain.com/debug-state
Should return: {"lobbyUsers":[],"rooms":[]}
If you can see the site in a browser over HTTP at this point — everything is working and ready for the certificate.
Step 12: Get Your SSL Certificate
Before running certbot, verify DNS and the challenge path are both working:
# Check DNS points to this server nslookup call.yourdomain.com
# Test the challenge path echo "test" > /opt/v4call/data/certbot/www/.well-known/acme-challenge/testfile curl http://call.yourdomain.com/.well-known/acme-challenge/testfile
The curl command must return test. If it does not, Nginx is not running — check docker compose ps and docker compose logs nginx.
When both work, get the certificate. The --entrypoint certbot flag is required — without it, Docker runs the container's default renewal loop instead of the certonly command:
docker compose run --rm \ --entrypoint certbot \ certbot certonly \ --webroot \ -w /var/www/certbot \ -d call.yourdomain.com \ --email your@email.com \ --agree-tos \ --no-eff-email
Replace call.yourdomain.com with your domain and your@email.com with your email.
Success looks like:
Requesting a certificate for call.yourdomain.com Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/call.yourdomain.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/call.yourdomain.com/privkey.pem This certificate expires on 2026-07-09.
Verify the files exist on the host:
ls /opt/v4call/data/certbot/conf/live/call.yourdomain.com/
Should show: cert.pem chain.pem fullchain.pem privkey.pem README
Step 13: Enable HTTPS in Nginx
Now the certificate exists, update Nginx to serve HTTPS.
nano /opt/v4call/nginx/v4call.conf
Replace everything with the full HTTPS config:
# HTTP — only used for certbot challenge and redirect to HTTPS
server {
listen 80;
server_name v4call.com v4call.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS — auth lives here
server {
listen 443 ssl;
server_name v4call.com v4call.com;
# CHANGE PATH: for ssl certifcates change v4call.com to your path: which you can find at "ls data/certbot/conf/live/"
ssl_certificate /etc/letsencrypt/live/v4call.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/v4call.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
# When user cancels the login prompt, send them here
error_page 401 /info.html;
# info.html is served directly by Nginx — no auth, no proxy
location = /info.html {
root /usr/share/nginx/html;
auth_basic off;
}
# WebSocket — requires auth
location /socket.io/ {
# uncomment to require username and password to enter site - useful for testing
# auth_basic "v4call — Private Testing";
# auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400;
}
# Everything else — requires auth, proxied to app
location / {
# uncomment to require username and password to enter site - useful for testing
# auth_basic "v4call — Private Testing";
# auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300;
proxy_send_timeout 300;
}
# This will log correct and incorrect attempts logging in, if .htpasswd used
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log;
}
- REPLACE
call.yourdomain.comwith your domain throughout (it appears 5 times). - CHANGE PATH: for ssl certifcates change v4call.com to your path: which you can find at
ls data/certbot/conf/live/if not sure.
ssl_certificate /etc/letsencrypt/live/v4call.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/v4call.com/privkey.pem;
Save and restart Nginx:
docker compose restart nginx
Check the logs — you should see no [emerg] errors:
docker compose logs nginx
The last lines should show:
nginx/1.x.x ... start worker processes
- If you see an error such as:
[emerg] 1#1: cannot load certificate "/etc/letsencrypt/live/call.yourdomain.com/fullchain.pem": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory:calling fopen(/etc/letsencrypt/live/call.yourdomain.com/fullchain.pem, r) error:10000080:BIO routines::no such file)
- Make sure you edited
nginx/v4call.confcorrectly. - To find the path for your SSL certs look in path
data/certbot/conf/live/ - There are 4 lines to check in
nginx/v4call.conf:- Line 4:
server_name call.yourdomain.com www.call.yourdomain.com; - Line 18:
server_name call.yourdomain.com www.call.yourdomain.com; - Line 20:
ssl_certificate /etc/letsencrypt/live/call.yourdomain.com/fullchain.pem; - Line 21:
ssl_certificate_key /etc/letsencrypt/live/call.yourdomain.com/privkey.pem;
- Line 4:
- When done restart nginx with:
docker compose restart nginx
Step 14: Set Up SSL Auto-Renewal
Let's Encrypt certificates expire after 90 days. A cron job renews them automatically.
crontab -e
Add this line at the bottom:
0 3 * * * cd /opt/v4call && docker compose run --rm --entrypoint certbot certbot renew --quiet && docker compose exec nginx nginx -s reload
Save and exit. This runs at 3am every day, renews if the cert is close to expiry, and reloads Nginx to pick up the new certificate.
Step 15: Test Everything is Working
Test HTTPS:
curl https://call.yourdomain.com/debug-state
Should return: {"lobbyUsers":[],"rooms":[]}
Test admin access:
curl "https://call.yourdomain.com/admin/balance?key=YOUR_ADMIN_KEY"
Should return your escrow account balance.
Open your browser and go to https://call.yourdomain.com
You should see the v4call login page with:
- A padlock icon in the browser address bar
- A green ⚡ Sign in with Keychain button (if Hive Keychain is installed)
- A manual posting key login option below it
- A 📖 New here? Learn the v4call basics → link at the bottom
Log in with Hive Keychain or a posting key to confirm the login flow works. 🎉
Step 16: Set Up Your Call Rates on Hive
For callers to be charged when they ring you, publish your rates on the Hive blockchain:
- Make sure Hive Keychain is installed in your browser
- Go to
https://call.yourdomain.com/rate-editor.html - Enter your Hive username
- Set your rates — ring fee, connect fee, duration rate per hour, minimum credit deposit
- Set
PLATFORM-FEEto at least your server'sDEFAULT_PLATFORM_FEEpercentage (e.g.10for 10%). If you set it lower, paid contacts to your account will be rejected on this server. - Optionally add custom token sections (e.g.
[TOKEN:CNOOBS]) to offer discounted rates for callers who hold your token - Click Generate to preview the rates block
- Click Post to Hive — Keychain will ask you to approve the post
This creates a post titled v4call-rates on your Hive blog. Your server reads this post automatically.
To verify your server read the rates correctly:
https://call.yourdomain.com/debug-rates/yourusername
To test with a specific caller (checks their token balances too):
https://call.yourdomain.com/debug-rates/yourusername?caller=theirusername&type=voice
You should see your rates as JSON, including which currency and rates apply for that caller.
Feature Guide: What Your Server Can Do
This section explains all the features available in v4call and how they work. No code changes needed — everything described here is built in and ready to use.
Login Options
v4call supports two ways to sign in:
- Hive Keychain (recommended) — click the green ⚡ Sign in with Keychain button. Keychain signs a challenge to prove your identity. No key paste needed. After login, a 🔑 panel appears in the lobby where you can optionally enter your posting key to unlock encrypted messaging (Keychain cannot expose private keys, so encryption needs the key entered once per session).
- Manual posting key — paste your Hive posting private key (starts with
5J) directly. The key stays in browser session memory only — never sent to the server.
Both methods verify your identity against the Hive blockchain.
Voice and Video Calls
Each online user in the lobby shows three action buttons:
- 📞 Green phone icon — start a voice-only call (audio, no camera)
- 🎥 Blue camera icon — start a video call (audio + camera)
- 💬 Purple chat bubble icon — open the DM panel to send a direct message
Voice calls request microphone only — no camera permission prompt. Video calls request both. The caller and callee can have different call types — the type is set by whoever initiates the call.
Separate rates can be set for voice and video calls in the rates post (voice is typically cheaper).
Direct Messages (DMs)
Click the purple 💬 button next to any online user to open the DM panel. DMs are end-to-end encrypted using Hive posting keys — the server stores only ciphertext it cannot read.
Chat storage: DMs are stored on the server in an encrypted database (v4call-chat.db) for up to DM_RETENTION_DAYS (default: 33 days). Both sender and recipient get their own encrypted copy stored, so both can retrieve their history later.
Unread alerts: When you log in, if you have unread DMs, a popup appears showing how many messages from how many users. Click a username in the popup to open their DM history.
DM previews: The last DM_PREVIEW_COUNT messages per conversation are loaded into the lobby chat on login, so you can see recent activity at a glance. Set to 0 in .env to disable.
Full history: Click the DM button for any user to load the complete conversation history, shown between "— DM history —" dividers.
Rooms
Users can create private rooms by selecting users in the lobby (toggle switch) and clicking Create & Invite. Rooms support encrypted messaging, video, and voice.
Room history: When a new user joins a room, they see past messages — broadcasts in full, encrypted messages only if they were addressed to them. A "— earlier messages —" divider separates history from live messages.
Ephemeral rooms: A warning banner at the top of every room says: "⚠ Room is ephemeral — if all users leave, the room and its history are deleted. New members can only read messages encrypted to their key." When the last person leaves a room, all stored messages for that room are deleted from the database.
Custom Token Payments (Hive-Engine)
v4call supports payment in any Hive-Engine token, not just HBD. This is configured per-user in their rates post using [TOKEN:SYMBOL] sections.
How it works:
- A user (e.g. @cnoobz) creates a custom token on Hive-Engine (e.g. CNOOBS)
- In their rates post, they add a
[TOKEN:CNOOBS]section with lower rates than their default HBD rates - When a caller who holds CNOOBS contacts @cnoobz, the server detects the token balance and offers the token rates
- If the caller holds multiple qualifying tokens, all options are shown in a currency picker — the caller chooses which to pay with
- The payment goes through Hive Keychain as a
custom_jsonHive-Engine transfer
For the escrow account: Your escrow account needs to hold some of each custom token that users on your server accept. Token payouts (to the callee) and refunds (to the caller) are sent from the escrow account's token balance, just like HBD.
Payment picker: When multiple payment options exist (e.g. CNOOBS at 1 per message, HBD at 100000 per message), the payment modal shows clickable currency buttons so the caller can see all rates and choose the best option.
Platform Fee System
The platform fee is how your server earns revenue from paid calls and DMs.
How it works:
- Your server's
DEFAULT_PLATFORM_FEEin.envis the minimum percentage your server accepts (e.g.10= 10%) - Each user sets
PLATFORM-FEEin their Hive rates post — this is the maximum fee they are willing to pay to a server - If the user's posted fee is lower than your server's minimum → rejected. The caller sees a message explaining the mismatch, and the callee is told to raise their fee.
- If the user's posted fee meets or exceeds your server's minimum → accepted, and the server charges its own rate (the minimum), not the user's higher number. The callee gets the best price.
- If the user has no
PLATFORM-FEEline in their rates post → the server's default is used automatically. No mismatch. - Free contacts (no payment involved) are never affected by fee enforcement.
Why this matters for federation: Different servers can set different platform fees. Users can shop around — pick a server with a fee they find agreeable. This creates a free market for server operators.
Example:
- Your server:
DEFAULT_PLATFORM_FEE=3(3%) - @alice posts:
PLATFORM-FEE: 5%→ accepted, server charges 3% (best price for alice) - @bob posts:
PLATFORM-FEE: 1%→ rejected, bob needs to raise to at least 3% - @charlie has no fee line → defaults to 3%, automatically accepted
Admin Configuration Reference
All settings are in the .env file. After changing any value, rebuild:
docker compose down && docker compose build --no-cache && docker compose up -d
| Variable | Default | Description |
|---|---|---|
SERVER_NAME |
v4call |
Display name for your server |
SERVER_DOMAIN |
v4call.com |
Your server's domain |
SERVER_HIVE_ACCOUNT |
v4call |
Hive account that receives platform fees |
ESCROW_ACCOUNT |
v4call-escrow |
Hive account that holds funds during calls |
V4CALL_ESCROW_KEY |
(none) | Active private key for the escrow account. Required. |
ADMIN_KEY |
(none) | Password for admin endpoints (/admin/balance, /admin/ledger)
|
DEFAULT_PLATFORM_FEE |
10 |
Server's minimum platform fee percentage |
DM_RETENTION_DAYS |
33 |
Days to keep stored DMs before cleanup |
ROOM_RETENTION_DAYS |
33 |
Days to keep stored room messages before cleanup |
DM_PREVIEW_COUNT |
1 |
Recent DMs per conversation shown on login (0 = off) |
HIVE_API |
(blank) | Override primary Hive API node. Blank = auto-select from built-in list |
MAX_CALL_DURATION_MIN |
120 |
Maximum call length in minutes before auto-disconnect |
CALL_COOLDOWN_MS |
30000 |
Milliseconds between call attempts to same user |
PAYMENT_VERIFY_RETRIES |
3 |
Number of attempts to verify a blockchain payment |
PAYMENT_VERIFY_DELAY_MS |
5000 |
Delay between verification retry attempts |
Debug Endpoints
These are useful for testing without making actual calls:
/debug-state— shows current lobby users and active rooms (no auth required)/debug-rates/USERNAME— shows parsed rates for a user/debug-rates/USERNAME?caller=CALLER&type=voice— shows what rates a specific caller would receive (checks token balances too)/admin/balance?key=YOUR_ADMIN_KEY— shows escrow account HBD balance/admin/ledger?key=YOUR_ADMIN_KEY— shows recent payment records
Updating Your Server
Important: Always use docker compose down before rebuilding. Without this step, Docker may reuse the old container even after a rebuild, and your changes will not take effect.
cd /opt/v4call docker compose down docker compose build --no-cache docker compose up -d
Your data, SQLite databases and .env config are preserved — they live in data/logs/ which is a mounted volume.
To pull updates from GitHub and deploy:
cd /opt/v4call git pull docker compose down docker compose build --no-cache docker compose up -d
To push your own customisations to GitHub:
# On your local computer after making changes: git add . git commit -m "describe what you changed" git push
# On the VPS: cd /opt/v4call git pull docker compose down docker compose build --no-cache docker compose up -d
Common Problems and Fixes
Changes not showing after rebuild
If you edited server.js or index.html but changes are not visible, you probably forgot to bring Docker down first. docker compose restart and even docker compose up -d --build can reuse old containers.
Fix:
docker compose down docker compose build --no-cache docker compose up -d
SqliteError: unable to open database file
The app container runs as UID 1000 but the logs directory was created by root. Fix:
chown -R 1000:1000 /opt/v4call/data/logs docker compose restart app
Certbot says "No renewals were attempted"
The certbot container's default behaviour is a renewal loop. Your certonly command is being ignored. Always use --entrypoint certbot to override it:
docker compose run --rm --entrypoint certbot certbot certonly ...
Without --entrypoint certbot the container runs its renewal script instead of your command.
Nginx crashes with "cannot load certificate: No such file"
You added the HTTPS server block before getting the certificate. Nginx reads all server blocks at startup — if the cert file doesn't exist, the entire process fails.
Fix: revert nginx config to HTTP-only (Step 9), restart Nginx, get the certificate (Step 12), then re-add HTTPS (Step 13).
Webroot challenge test returns nothing
Nginx is not running. Check:
docker compose ps docker compose logs nginx
If Nginx is restarting — it has the HTTPS config with a missing cert file. Use the HTTP-only config.
"Escrow key does NOT match" warning on startup
The key in .env is the wrong type. You need the active private key, not the posting or owner key. Find it in your Hive wallet → Keys & Permissions → Active. It starts with 5K.
Site unreachable on port 443
Check that the certificate was issued:
ls /opt/v4call/data/certbot/conf/live/
Should show a folder with your domain name. If empty — go back to Step 12.
npm ci error during Docker build
The npm ci command requires a package-lock.json file. The project uses npm install instead. If you see this error, check your Dockerfile — it should say npm install not npm ci.
Custom token payments not working
If token rates are detected but payments fail:
- Check the escrow account holds the token — send some tokens to your escrow account on Hive-Engine
- Check the token symbol matches exactly (case-sensitive) between the rates post and Hive-Engine
- Check the server logs:
docker compose logs app | grep -i "token\|cnoobs\|escrow-token" - The Hive-Engine API endpoint must be
https://api.hive-engine.com/rpc/contracts— this is built into the code
[encrypted — unlock with 🔑 key panel to read]
You logged in with Hive Keychain. Keychain does not expose private keys, so encrypted messages cannot be decrypted without your posting key. Enter your posting key in the 🔑 panel at the bottom of the online users list. The key stays in browser session memory only — it is needed once per session.
Quick Reference
| Command | What it does |
|---|---|
docker compose ps |
Show status of all containers |
docker compose logs app |
Show app logs |
docker compose logs nginx |
Show Nginx logs |
docker compose logs -f app |
Watch live logs (Ctrl+C to stop) |
docker compose down |
Stop everything (always do this before rebuilding) |
docker compose build --no-cache |
Rebuild without using cached layers |
docker compose up -d |
Start everything in background |
docker compose down && docker compose build --no-cache && docker compose up -d |
Full rebuild cycle (use after any code change) |
docker compose restart nginx |
Restart Nginx after config-only changes (no rebuild needed) |
chown -R 1000:1000 /opt/v4call/data/logs |
Fix SQLite write permissions |
docker compose run --rm --entrypoint certbot certbot certificates |
List SSL certificates |
curl http://yourdomain.com/.well-known/acme-challenge/testfile |
Test certbot webroot works |
| escrow" | Check token payment logs |
Optional: Password Protect Your Server During Testing
Optional: Password Protect Your Server During Testing (HTTP Basic Auth)
During development and testing you may want to restrict access so only people you invite can use your server. This uses Nginx HTTP Basic Auth — a simple username and password prompt that appears before the v4call login screen.
When a visitor cancels the login prompt they are shown a public info.html page where they can read about the project and request access.
- Step 1 — Install the htpasswd tool
apt install -y apache2-utils
- Step 2 — Create the password file and add your first user
htpasswd -c /opt/v4call/nginx/.htpasswd yourusername
You will be prompted to enter and confirm a password. The -c flag creates the file. Do not use -c again or it will overwrite the file and delete existing users.
To add more users later:
htpasswd /opt/v4call/nginx/.htpasswd anotherusername
To remove a user:
htpasswd -D /opt/v4call/nginx/.htpasswd username
- Step 3 — Create the public info page
nano /opt/v4call/public/info.html
Paste your HTML content — a page explaining the project and how to request access. This page is served publicly without a password so visitors who cancel the login prompt can still read it.
- Step 4 — Mount the files into the Nginx container
Edit docker-compose.yml and add two lines to the nginx volumes section:
nginx:
volumes:
- ./nginx/v4call.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro # add this
- ./public/info.html:/usr/share/nginx/html/info.html:ro # add this
- ./data/certbot/conf:/etc/letsencrypt:ro
- ./data/certbot/www:/var/www/certbot:ro
- Step 5 — Update your Nginx config
Add auth to the HTTPS server block (not the HTTP block — users are redirected to HTTPS so auth must live there). Add these lines to both the /socket.io/ and / location blocks, and add the error_page and info.html location blocks:
server {
listen 443 ssl;
server_name call.yourdomain.com www.call.yourdomain.com;
# ... ssl_certificate lines stay the same ...
# Send cancelled logins to the public info page
error_page 401 /info.html;
# info.html is served by Nginx directly — no auth, no proxy
location = /info.html {
root /usr/share/nginx/html;
auth_basic off;
}
# WebSocket — requires auth
location /socket.io/ {
auth_basic "v4call — Private Testing";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://app:3000;
# ... rest of proxy headers stay the same ...
}
# All other requests — requires auth
location / {
auth_basic "v4call — Private Testing";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://app:3000;
# ... rest of proxy headers stay the same ...
}
}
- Note: if you want to log successful and unsuccessful login attempts, add:
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log;
- Step 6 — Recreate the Nginx container to pick up the new volume mounts
Important: docker compose restart nginx is not enough — it reuses the old container and ignores new volume mounts. You must recreate it:
docker compose down && docker compose up -d
After this, verify the files are mounted inside the container:
docker compose exec nginx ls /usr/share/nginx/html/
You should see info.html listed alongside 50x.html and index.html.
- Step 7 — Test it works
curl -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/
Should return 401 (login required).
curl -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/info.html
Should return 200 (public, no auth needed).
curl -u youruser:yourpassword -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/
Should return 200 (correct credentials accepted).
- Managing users without restarting
After adding or removing users from the .htpasswd file, reload Nginx config without downtime:
docker compose exec nginx nginx -s reload
No restart needed — Nginx re-reads the .htpasswd file on every request anyway.
- Keep .htpasswd out of GitHub
The password file should never be committed to your repository. Add it to .gitignore:
echo "nginx/.htpasswd" >> /opt/v4call/.gitignore
- Removing auth when you go public
When you are ready to open your server to everyone, simply remove the auth_basic lines from nginx/v4call.conf and reload:
docker compose exec nginx nginx -s reload
No other changes needed.
How to check if anyone tried to log in (failed attempts) or successfully logged in with .htpasswd on Nginx in Docker
Important Tip: Always run these commands in the same folder where your docker-compose.yml file is located. If you are in the wrong directory the commands will not find your container and nothing will show up.
- Basic Commands
- See everything live (good while testing)
docker compose logs -f nginx-fmeans "follow/live" – new log lines appear automatically. Remove-fif you only want to read the current logs once and stop.
- Best command to watch failed login attempts (people guessing passwords)
docker compose logs -f nginx | grep -E "mismatch|not found|401"
- See who successfully logged in
docker compose logs nginx | grep "remote_user"
- Combined view – most useful for daily checking (failed + successful)
docker compose logs -f --tail=100 nginx | grep -E "(mismatch|not found|remote_user|401)"
- What the Logs Look Like
Failed attempt (wrong password):
2026/04/17 01:23:45 [error] ... user "admin": password mismatch, client: 185.123.45.67, ...
Failed attempt (wrong username):
2026/04/17 01:24:12 [error] ... user "hacker123" was not found in "/etc/nginx/.htpasswd", client: 45.67.89.10, ...
Successful login:
... "GET /protected/ HTTP/1.1" 200 ... remote_user: "myuser" ...
- How to Customise These Commands
- Change
nginxto the exact name of your service if it is different in docker-compose.yml. - Remove
-fto read the full log once without live following. - Change
--tail=100to--tail=500(or any number) to show more or fewer old lines. - Add or remove words in the
greppart to filter differently.
Examples: * Only failed attempts:grep -E "mismatch|not found"* Only 401 errors:grep " 401 "* Everything auth related:grep -E "(auth|password|mismatch|not found|remote_user)"
- Quick Copy-Paste Commands
- Watch failed guesses live
docker compose logs -f nginx | grep -E "mismatch|not found|401"
- Check successful logins
docker compose logs nginx | grep "remote_user"
- Combined quick check (recommended)
docker compose logs -f --tail=100 nginx | grep -E "(mismatch|not found|remote_user|401)"