Connect via WebSocket
Program your device to open a WebSocket connection, authenticate, and start sending data. Copy-paste examples for Node.js, Python, and ESP32.
Not deployed yet? Check out the Deployment Options page to choose between DigitalOcean, Railway, or Fly.io — especially if your device can't do TLS.
WebSocket URL
wss://[DOMAIN]/wsProduction (port 443, TLS)
ws://localhost:3000/wsLocal dev only
Protocol
JSON (generic) or XML (biometric)Port 3000 is internal only. Use wss://YOUR_DOMAIN (port 443) for production. Port 3000 only works for local development. Get your device_id and token from the Devices page after adding your device.
| Device Field | Set To | Notes |
|---|---|---|
| Server mode | Yes | Enables cloud / server connection mode. |
| Use domainNm | Yes | Connect by domain name instead of a raw IP. |
| DomainNm | iotaccess.xyz | Domain only — no https://, no port, no /ws path. |
| SerPortNo | 443 | Public TLS port. Do not use 3000 (internal only). |
| Heartbeat | 3 | Keep-alive interval (vendor default is fine). |
| Server approval | No | Device still appears as a pending "Detected" terminal to approve in the dashboard. |
Port 443 means TLS. Railway terminates TLS at its edge and only exposes the public domain on 443, so the terminal must negotiate a secure (wss://) connection. If your firmware has an SSL/TLS toggle, enable it. The internal port 3000 is not reachable from outside the container.
The server accepts WebSocket upgrades on any path, so whatever path the firmware uses internally will work — you don't need to configure /ws. Watch connections arrive live under Settings → Port watch.
ws:// cannot connect on 443, because the public domain forces TLS. Expose a raw, non-TLS TCP port instead.Why 443 fails without TLS:Railway's edge terminates TLS on the public domain (iotaccess.xyz), so a plain ws:// handshake on 443 is rejected. There is no way to turn TLS off on the main domain — you need a separate raw TCP port.
In Railway → your service → Settings → Networking → TCP Proxy, click Add TCP Proxy and set the target port to 8080. Railway returns a public host:port like turntable.proxy.rlwy.net:23456.
In Railway → Variables, add WS_TCP_PORT=8080 (must match the target port above), then redeploy. The server starts a plain-WebSocket listener on that port.
Point the device's Server menu at the proxy endpoint Railway gave you.
| Device Field | Set To (example) | Notes |
|---|---|---|
| Use domainNm | Yes | Connect by the proxy host name. |
| DomainNm | turntable.proxy.rlwy.net | The proxy host from Railway — not iotaccess.xyz. |
| SerPortNo | 23456 | The external port Railway assigned to the proxy. |
This connects as plain ws://turntable.proxy.rlwy.net:23456 — no TLS, no certificates. The TCP proxy forwards raw bytes straight to the container, and the server accepts the WebSocket upgrade on any path. Confirm it arrives live under Settings → Port watch.
Connect
Open WebSocket
Register
Send device_id + token
Authenticated
Receive confirmation
Communicate
Send/receive messages
const WebSocket = require("ws")
// For production: use wss:// with port 443 (HTTPS/TLS)
// For local dev: use ws:// with port 3000
const WS_URL = "wss://YOUR_SERVER_DOMAIN/ws" // or "ws://localhost:3000/ws" for local dev
const DEVICE_ID = "your-device-uuid" // From dashboard
const TOKEN = "tok_your_device_token" // From dashboard
let ws = null
let reconnectTimer = null
function connect() {
console.log("Connecting to", WS_URL)
ws = new WebSocket(WS_URL)
ws.on("open", () => {
console.log("Connected! Registering...")
ws.send(JSON.stringify({
type: "register",
device_id: DEVICE_ID,
token: TOKEN
}))
})
ws.on("message", (data) => {
const msg = JSON.parse(data.toString())
console.log("Received:", msg)
if (msg.type === "command") {
handleCommand(msg.command, msg.payload)
}
})
ws.on("close", () => {
console.log("Disconnected. Reconnecting in 5s...")
reconnectTimer = setTimeout(connect, 5000)
})
ws.on("error", (err) => {
console.error("WebSocket error:", err.message)
})
}
function handleCommand(command, payload) {
console.log("Executing command:", command, payload)
// Implement your device-specific logic here
}
function sendTelemetry(data) {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: "telemetry",
payload: data
}))
}
}
// Start connection
connect()
// Send telemetry every 30 seconds
setInterval(() => {
sendTelemetry({
temperature: 22.5 + Math.random() * 2,
humidity: 45 + Math.random() * 10
})
}, 30000)Required Changes
- • Replace
YOUR_SERVER_IPwith actual server address - • Replace
DEVICE_IDwith UUID from dashboard - • Replace
TOKENwith your device token
Testing
- • Run the code on your device
- • Check the dashboard for "Online" status
- • Send a test command from the dashboard
| Direction | Type | Purpose |
|---|---|---|
| Device → Server | register | Authenticate with device_id + token |
| Device → Server | telemetry | Send sensor data / readings |
| Device → Server | event | Report device events |
| Device → Server | ping | Heartbeat (server replies with pong) |
| Server → Device | command | Execute action on device |
| Server → Device | registered | Confirmation of successful registration |
- Device appears as 'Online' in the dashboard
- Device name shows in the Devices list
- Event stream shows 'device_connected' event
- Telemetry data appears on device detail page
Devices that can't establish persistent WebSocket connections can use the HTTP/HTTPS protocol instead. All communication is via POST requests with JSON payloads and JSON responses.
1. Register on Startup
POST /api/device-http
Content-Type: application/json
{
"cmd": "reg",
"sn": "YOUR_DEVICE_SN",
"cpusn": "CPU_SERIAL",
"devinfo": {
"modelname": "device-model",
"usersize": 1000,
"firmware": "v1.0.0",
"mac": "00:11:22:33:44:55"
}
}
Response:
{
"ret": "reg",
"result": true,
"cloudtime": "2026-06-12T10:00:00Z"
}2. Send Attendance Logs
POST /api/device-http
Content-Type: application/json
{
"cmd": "sendlog",
"sn": "YOUR_DEVICE_SN",
"count": 1,
"logindex": 0,
"record": [{
"enrollid": 1,
"time": "2026-06-12T09:15:30",
"mode": 1,
"inout": 0,
"event": 0,
"temp": 36.5
}]
}
Response:
{
"ret": "sendlog",
"result": true,
"cloudtime": "2026-06-12T09:16:00Z"
}3. Heartbeat (Every 60s)
POST /api/device-http
Content-Type: application/json
{
"cmd": "checklive",
"sn": "YOUR_DEVICE_SN",
"time": "2026-06-12T09:20:00"
}
Response:
{
"ret": "checklive",
"result": true,
"cloudtime": "2026-06-12T09:20:05Z"
}Python Example
import requests
import json
from datetime import datetime
URL = "https://your-host/api/device-http"
SN = "YOUR_DEVICE_SN"
# 1. Register device
def register():
payload = {
"cmd": "reg",
"sn": SN,
"cpusn": "12345",
"devinfo": {"modelname": "device", "firmware": "v1.0"}
}
r = requests.post(URL, json=payload)
print("Registered:", r.json())
# 2. Send attendance log
def send_log():
payload = {
"cmd": "sendlog",
"sn": SN,
"count": 1,
"record": [{
"enrollid": 123,
"time": datetime.now().isoformat(),
"mode": 1,
"inout": 0
}]
}
r = requests.post(URL, json=payload)
print("Sent log:", r.json())
# 3. Send heartbeat
def heartbeat():
payload = {
"cmd": "checklive",
"sn": SN,
"time": datetime.now().isoformat()
}
r = requests.post(URL, json=payload)
print("Heartbeat:", r.json()['result'])
if __name__ == "__main__":
register()
send_log()
heartbeat()checklive heartbeat every 60 seconds to keep the device marked as online. Without it, the device will be marked offline after 5 minutes of inactivity.