Is there websocket api documentation for WiiM Pro?

JohnHW

New member
Joined
Oct 26, 2025
Messages
3
Hi

I'm using Node-RED to control playback on my WiiM Pro using the HTTPS API (WiiM's document on that was great), and it works just fine. However, I'd like to be able to detect events on the WiiM, such as a track change or playing starting from a source client such as Roon or Spotify - so as to be able to trigger the appropriate settings of the rest of the playback system automatically, such as power other components on, choose appropriate input settings and so on.

I know I could just regularly poll using the HTTPS API and detect changes, but that introduces delay and is really inefficient. Ideally I'd like to establish a websocket connection and subscribe to the relevant events - is there any documentation about that, or can anyone advise whether that's possible on the WiiM?

Thanks
 
As far as I understand this is possible through UPnP-API. I haven’t done this by myself nor have I seen documentation of this explicitly for WiiM/Linkplay but it should be standard so look at any UPnP documentation for this
 
Thanks @Valimir , that's a helpful pointer to something I've never used in this way. I've spent some time blundering around on this, and made some progress. I found a free app for Android, UPnP Tool, which began to unlock the issue. I've managed to get Node-RED to subscribe to the relevant event service (or at least I think I have) but I'm not managing to get to access those events in Node-RED. I say I think I've subscribed successfully, because WiiM responds with:

Screenshot 2025-10-27 at 08.43.15.png
My SUBSCRIBE http request includes the necessary CALLBACK header (<http://192.168.11.199:24879>), although there's no explicit confirmation in this response. Perhaps that's the problem. I found Node-RED as standard can't http listen on another port other than the configurable main web port, but a bespoke node (node-red-contrib-http-custom-port) purports to facilitate this. However, I don't get any event notifications listening on this port, and that's where I'm now stuck.

Screenshot 2025-10-27 at 08.51.36.png

Any suggestions welcome.
 
ChatGPT:
Native UPnP “eventing” works via SUBSCRIBE and NOTIFY (GENA). WiiM implements the standard AVTransport & RenderingControl services (so they’ll send LastChange events). However, Node-RED’s built-in HTTP In node doesn’t accept the NOTIFY verb, so the usual pattern is a tiny Node.js sidecar that handles the GENA subscription + renewals and then POSTs parsed events into Node-RED.

1) Discover the WiiM + find service URLs
  • SSDP: search for device type urn:schemas-upnp-org:device:MediaRenderer:1 (or WiiM’s proprietary urn:schemas-upnp-org:device:LinkplayRenderer:1). Grab the LOCATION URL, then fetch the device description to get AVTransport & RenderingControl eventSubURL and controlURL.
    (You can automate discovery with node-ssdp, or mDNS _linkplay._tcp.)

2) Run a tiny “bridge” script
cd ~/.node-red
npm install node-upnp-subscription express body-parser
Create wiim-upnp-bridge.js:


[

JavaScript:
const http = require("http");
const express = require("express");
const bodyParser = require("body-parser");
const Subscription = require("node-upnp-subscription"); // manages SUBSCRIBE/renewals

// ---- customize these:
const WIIM_BASE = "http://<WiiM-IP>:<port-from-ssdp-location>"; // e.g. http://192.168.1.50:49152
const AVT_EVENT_SUB = "/upnp/event/AVTransport1";              // from device description
const RC_EVENT_SUB  = "/upnp/event/RenderingControl1";         // from device description
const NODE_RED_POST = "http://127.0.0.1:1880/wiim/events";     // where we'll forward parsed events
const CALLBACK_PORT = 4005;                                    // local port for NOTIFY callbacks
// -----------------------

const app = express();
app.use(bodyParser.text({ type: "*/*" })); // raw XML from NOTIFY

app.post("/gena", (req,res)=>{ // for devices that use POST (rare)
  forward(req.body); res.sendStatus(200);
});

// Our NOTIFY listener (GENA uses method NOTIFY, not POST)
const server = http.createServer((req,res)=>{
  if (req.method === "NOTIFY") {
    let data=""; req.on("data",chunk=>data+=chunk);
    req.on("end", ()=>{ forward(data); res.writeHead(200); res.end(); });
  } else { res.writeHead(405); res.end(); }
});
server.on("request", app);
server.listen(CALLBACK_PORT, ()=>console.log("GENA listening on", CALLBACK_PORT));

function forward(xml) {
  // forward to Node-RED as JSON blob {raw:xml}
  const payload = JSON.stringify({ raw: xml });
  const u = new URL(NODE_RED_POST);
  const req = http.request({hostname:u.hostname,port:u.port,path:u.pathname,method:"POST",
    headers:{"content-type":"application/json","content-length":Buffer.byteLength(payload)}});
  req.on("error", console.error);
  req.end(payload);
}

function subTo(servicePath) {
  const url = WIIM_BASE + servicePath;
  const cb  = `http://${getLocalIP()}:${CALLBACK_PORT}/gena`; // some stacks require angle brackets; lib handles it
  const s = new Subscription(url, cb, 300); // 5min timeout auto-renew
  s.on("message", (msg)=> forward(msg && msg.body ? msg.body : String(msg)));
  s.on("error", console.error);
  s.subscribe();
  return s;
}

function getLocalIP(){
  const os = require("os");
  for (const ifs of Object.values(os.networkInterfaces())) {
    for (const i of ifs) if (!i.internal && i.family==="IPv4") return i.address;
  }
  return "127.0.0.1";
}

// subscribe to both AVTransport + RenderingControl
const subs = [ subTo(AVT_EVENT_SUB), subTo(RC_EVENT_SUB) ];
process.on("SIGINT", ()=>{ subs.forEach(s=>s.unsubscribe()); process.exit(0); });
Run it (alongside Node-RED):
node wiim-upnp-bridge.js
 
Thanks again @Valimir. However, we're now way outside what I understand! Everything I'm running is headless once configured. My Node-RED instance runs in a Docker container on a Synology NAS using macvlan to allow each container its own IP address. Not sure what I'd need to do to run this JavaScript bridge script alongside Node-RED, and ensure the script always relaunches automatically when the container restarts. If I could work that out, I guess I could fumble through the unintelligible JS and customise it ... All just because Node-RED's http in node doesn't understand the verb NOTIFY.
 
Back
Top