Intaleq Voice Call Signaling Backend (API Key & Session Pre-creation)
Production-ready WebRTC signaling server in Node.js for the Intaleq ride-hailing application. Coordinates secure peer-to-peer audio calls between driver and passenger during active rides only. Sessions are pre-created by the main application backend over an authenticated HTTP endpoint, allowing mobile clients to connect using temporary session IDs without dealing with JWTs.
Prerequisites
- Node.js 20+ (for local development)
- Docker and Docker Compose (for production deployments)
- Nginx (acting as reverse proxy for WSS upgrade and SSL termination)
- Let's Encrypt (for SSL certificates)
Local Development
-
Clone and Navigate Ensure you are in the
voice-call-servicedirectory. -
Setup Configuration Copy the example environment file and configure variables:
cp .env.example .envNote: Ensure
API_KEYis at least 32 characters long. -
Install Dependencies
npm install -
Run Server Since Node.js 20.6.0+, you can load
.envfiles natively using the--env-fileflag:node --env-file=.env cmd/server/main.jsThe signaling server will start and bind locally to
127.0.0.1:47880.
Docker Deployment
Deploying with Docker isolates dependencies and bounds resources to 128MB.
-
Deploy Command Run the deployment automation script:
./deploy.shThis script will verify your configuration, build a secure multi-stage container running under user
node(UID 1000), deploy it in host networking mode, and verify the service's health. -
Manual Docker Compose Commands Alternatively, you can build and run it manually:
docker-compose up -d --build
Nginx Configuration
An Nginx configuration block should be configured on your CloudPanel/hosting server for calls.intaleqapp.com. It proxies connections to localhost and processes WebSocket handshakes:
server {
listen 80;
server_name calls.intaleqapp.com;
location / {
proxy_pass http://127.0.0.1:47880;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Headers
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;
# Read timeout management
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}
Environment Variables
| Variable | Description | Default |
|---|---|---|
SERVER_ADDR |
Local binding interface and port | 127.0.0.1:47880 |
API_KEY |
Secret key for authenticating HTTP session creation | None (min 32 chars) |
LOG_LEVEL |
Level of logging output (debug, info, warn, error) |
info |
MAX_MESSAGE_BYTES |
Maximum payload size allowed for WebSocket frames | 4096 |
HEARTBEAT_INTERVAL |
Keepalive ping interval | 20s |
HEARTBEAT_TIMEOUT |
Connection death timeout | 30s |
SESSION_DURATION |
Hard limit on call length before auto-termination | 60s |
RATE_LIMIT_PER_MIN |
Maximum connections allowed per IP per minute | 10 |
ENVIRONMENT |
Target environment designation | production |
DB_HOST |
MySQL database host address | 127.0.0.1 |
DB_PORT |
MySQL database connection port | 3306 |
DB_DATABASE |
MySQL database name | callDB |
DB_USERNAME |
MySQL database username | None |
DB_PASSWORD |
MySQL database password | None |
Database Logging
The signaling server integrates with a MySQL database to log call connectivity metrics.
The system automatically creates a call_logs table on startup (defined in database.sql).
Call Logs Schema
| Column | Type | Description |
|---|---|---|
id |
INT |
Primary Key, Auto Increment |
session_id |
VARCHAR(36) |
Unique Ephemeral Session ID |
ride_id |
VARCHAR(255) |
Active Ride ID |
driver_id |
VARCHAR(255) |
Registered Driver ID |
passenger_id |
VARCHAR(255) |
Registered Passenger ID |
status |
VARCHAR(50) |
Call state (created, active, ended) |
initiated_by |
VARCHAR(255) |
User ID of the participant who started the connection (sent first offer) |
end_reason |
VARCHAR(255) |
Call termination reason (e.g., user_terminated, max_duration_reached, disconnect_driver, disconnect_passenger) |
created_at |
TIMESTAMP |
Time when session was pre-created |
connected_at |
TIMESTAMP |
Time when both parties connected and call started |
ended_at |
TIMESTAMP |
Time when call was terminated |
HTTP Session Pre-creation API
The main application backend (PHP) registers a session before allowing call access.
Create Call Session
- Route:
POST /sessions - Headers:
X-API-Key: YOUR_API_KEY_HEREContent-Type: application/json
- Request Body:
{ "ride_id": "ride_456", "driver_id": "user_123", "passenger_id": "user_789" } - Response Body (200 OK):
{ "session_id": "3b2e7c4f-95a2-4a0b-99f6-fc935d0a4461", "ride_id": "ride_456", "expires_in": 60 }
WebSocket Protocol Reference
Clients communicate with the backend at ws://calls.intaleqapp.com/ws.
Client-to-Server Messages
- Authenticate (MUST be sent first)
{ "type": "authenticate", "session_id": "3b2e7c4f-95a2-4a0b-99f6-fc935d0a4461", "user_id": "user_123" } - Offer WebRTC Payload
{"type": "offer", "sdp": "v=0\r\n..."} - Answer WebRTC Payload
{"type": "answer", "sdp": "v=0\r\n..."} - ICE Candidate WebRTC Payload
{"type": "ice_candidate", "candidate": {"candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0}} - Heartbeat
{"type": "heartbeat"} - End Call
{"type": "end_call"}
Server-to-Client Messages
- Authentication Success Alert
{"type": "authenticated", "user_id": "user_123"} - Session Joined (Active status)
{"type": "session_joined", "session_id": "sess_abc", "ride_id": "ride_456"} - Peer Joined Notification
{"type": "participant_joined", "role": "passenger"} - Relayed WebRTC Offer
{"type": "offer", "sdp": "v=0\r\n..."} - Relayed WebRTC Answer
{"type": "answer", "sdp": "v=0\r\n..."} - Relayed ICE Candidate
{"type": "ice_candidate", "candidate": {"candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0}} - Peer Disconnected Alert
{"type": "participant_left", "role": "driver"} - Call Duration Timeout Alert
{"type": "call_timeout", "reason": "max_duration_reached"} - Call Terminated Alert
{"type": "call_ended", "reason": "user_terminated"} - Heartbeat Reply (Pong)
{"type": "pong"} - Unauthorized Connection Error
{"type": "unauthorized", "reason": "session_not_found"} - General Error Message
{"type": "error", "code": "session_not_found", "message": "No active session for this ride"}
Security Notes
-
Access Control Only your trusted main backend can create call sessions, verified via the
X-API-Keyheader. Mobile clients only receive the ephemeralsession_idand can only authenticate under their pre-registereduser_idcontext. -
Encryption SSL (WSS) is terminated at Nginx. Internal network loops (from Nginx to 47880) run local loopbacks.