Video intercoms Β· Dahua 2-wire field guide
The door station has a web page. The indoor monitor often doesn't. This is how to configure it from a keyboard instead of its on-screen menu.
Part 5 of 7 Β· By Gareth Barber, Owner & Head Locksmith, Access Locksmiths Β· Published 2026-10-05
The door station (VTO) exposes a familiar web UI and the legacy CGI. The indoor monitor (VTH) often does not β no browser page, and the legacy CGI returns 404. The monitor speaks only the newer RPC2 JSON-RPC API. That API is what let us fix a monitor remotely.
RPC2 uses a challenge/response login. Reuse the returned session for every later call.
Stage 1: POST /RPC2_Login
{"method":"global.login",
"params":{"userName":"admin","password":"","clientType":"Web5.0"},
"id":1,"session":0}
-> {"params":{"realm":"...","random":"..."},"session":<S>,"result":false}
Stage 2: hash = MD5( "admin:REALM:PASSWORD" ).upper()
hash = MD5( "admin:RANDOM:" + hash ).upper()
POST /RPC2_Login
{"method":"global.login",
"params":{"userName":"admin","password":hash,"clientType":"Web5.0",
"realm":REALM,"random":RANDOM,"passwordType":"Default"},
"id":2,"session":<S>}
-> {"result":true,"session":<S>}
POST /RPC2 {"method":"configManager.getConfig","params":{"name":"SIP"}, "id":n,"session":S}
POST /RPC2 {"method":"configManager.setConfig","params":{"name":"SIP","table":{...}}, "id":n,"session":S}
POST /RPC2 {"method":"magicBox.reboot","params":{}, "id":n,"session":S}
Useful config names on a monitor: Network, SIP, VTOInfo, RemoteDevice.
SIPServer, OutboundProxy, Proxy and STUNServer to the door station's IP.VTOInfo / RemoteDevice) to the door station's IP and disable stray entries. This is the "network abnormal" fix from Part 2, done from a keyboard.The same API set the monitor's credentials, rebooted it, and made its IP static with a write to the Network table.
import json, hashlib, ssl, urllib.request
def md5up(s): return hashlib.md5(s.encode()).hexdigest().upper()
ctx = ssl._create_unverified_context() # device uses a self-signed cert
def call(ip, path, body):
req = urllib.request.Request(f"http://{ip}{path}",
data=json.dumps(body).encode(), headers={"Content-Type":"application/json"})
return json.loads(urllib.request.urlopen(req, context=ctx, timeout=15).read())
def login(ip, user, pwd):
r1 = call(ip, "/RPC2_Login", {"method":"global.login",
"params":{"userName":user,"password":"","clientType":"Web5.0"},"id":1,"session":0})
s, p = r1["session"], r1["params"]
h = md5up(f'{user}:{p["realm"]}:{pwd}'); h = md5up(f'{user}:{p["random"]}:{h}')
r2 = call(ip, "/RPC2_Login", {"method":"global.login",
"params":{"userName":user,"password":h,"clientType":"Web5.0",
"realm":p["realm"],"random":p["random"],"passwordType":"Default"},
"id":2,"session":s})
return r2.get("session", s)
Notes: the hash is uppercase MD5. The monitor serves RPC2 over HTTP on port 80. A scanner may show no web services because SIP is UDP β don't let that put you off.
Where a web UI exists, the door station also answers classic CGI with HTTP Digest auth:
GET /cgi-bin/magicBox.cgi?action=getDeviceType
GET /cgi-bin/configManager.cgi?action=getConfig&name=Network
GET /cgi-bin/configManager.cgi?action=setConfig&Network.eth0.IPAddress=...
Use curl -g so the [0] in names like DnsServers[0] isn't treated as a URL glob. Full examples are in Part 1.
Written from one real deployment. Client-identifying details have been removed; technical values are generic examples. Firmware and hardware generations differ β verify each step against your own models. Corrections are welcome and will be published with attribution.