While building a CLI tool against the documented protocol, I found several breaking differences in Insta360 Link Controller v2.2.1 (macOS). Documenting here for anyone hitting the same issues.
- Process name changed
The macOS process is no longer named Insta360 Link Controller. It now runs as Webcam-desktop inside the app bundle:
/Applications/Insta360 Link Controller.app/Contents/MacOS/Webcam-desktop
This matters if you're using pgrep or lsof to find the process — filtering by process name won't work. Filter by PID instead:
pgrep -f "Insta360 Link Controller" # matches the full path, still works
lsof -i -a -n -P -p # note: requires -a to AND the filters on macOS
- Port is ephemeral, not fixed
The WebSocket server no longer listens on a predictable port (7878, 9090, etc.). In testing it bound to 49924, which is in the ephemeral range. Discovery via lsof -p is the reliable approach.
- Token is required — in the URL and the controlRequest
The WebSocket connection now requires a token, passed in two places:
ws://localhost:/?token=
AND in the controlRequest protobuf message:
controlRequest { token: "" }
Connecting without the token (or with a wrong one) results in controlResponse { success: false, reason: TOKEN_INVALID }.
The token is generated by the app when it displays the QR code for the mobile web remote. Previously-accepted tokens are cached in:
~/Library/Application Support/Insta360 Link Controller/startup.ini
under a [Token] section, with Unix timestamps as values (so you can pick the most recent):
[Token]
3cAKW91uM3JxnlDCOzY9b55PnYHlWh=1773071792
JUW5b7Nf7NjxR9jpfuJxAxs1HUwmXN=1773071791
Note: configparser lowercases keys by default — read the file manually to preserve case, as the token is case-sensitive.
- DeviceInfoNotification field layout changed
The field assignments in DeviceInfoNotification are swapped compared to the proto:
| Field |
Proto (v1.4.1 doc) |
Actual v2.2.1 |
| 1 |
string curDeviceSerialNum |
DeviceInfo device (embedded msg) |
| 2 |
repeated DeviceInfo devices |
string curDeviceSerialNum |
So curDeviceSerialNum is now at field 2, and field 1 holds a single DeviceInfo message directly.
- DeviceInfo.zoom moved from field 3 to field 5
Within DeviceInfo, the ZoomInfo zoom message moved:
| Field |
Proto (v1.4.1 doc) |
Actual v2.2.1 |
| 3 |
ZoomInfo zoom |
int32 (varint) |
| 5 |
bool mirror |
ZoomInfo zoom |
The ZoomInfo content itself is unchanged (curValue, minValue, maxValue).
Raw hex capture
Full deviceInfoNotify response frame from a live session (serial redacted):
080152b2020a9f020a0b4c696e6b2d58585858585858120e5858585858585858585858585858
180020002a070864106418900338ffffffffffffffffff014000480052e301080110012001
2801300048016032683270327832880101900100980100a00100a80101b001fc2ad8010090
0202980200a80201b00200c00200d00201d80201e00200e80200f00200f80200800301880300
900301980300a2030a686f72697a6f6e74616cad0300000000b00300b80300c00300ca032c
08ffffffffffffffffff0110ffffffffffffffffff0118ffffffffffffffffff0120ffffffff
ffffffffffffffffff01d2031608ffffffffffffffffff0110ffffffffffffffffff01da0313
0800120723303066663030184d2009280a3032e00300e80301f00300f8035a800400880401
120e5858585858585858585858585858
Tested on: Link Controller 2.2.1 (build 24), macOS 26.3.1, Insta360 Link (original).
While building a CLI tool against the documented protocol, I found several breaking differences in Insta360 Link Controller v2.2.1 (macOS). Documenting here for anyone hitting the same issues.
The macOS process is no longer named Insta360 Link Controller. It now runs as Webcam-desktop inside the app bundle:
/Applications/Insta360 Link Controller.app/Contents/MacOS/Webcam-desktop
This matters if you're using pgrep or lsof to find the process — filtering by process name won't work. Filter by PID instead:
pgrep -f "Insta360 Link Controller" # matches the full path, still works
lsof -i -a -n -P -p # note: requires -a to AND the filters on macOS
The WebSocket server no longer listens on a predictable port (7878, 9090, etc.). In testing it bound to 49924, which is in the ephemeral range. Discovery via lsof -p is the reliable approach.
The WebSocket connection now requires a token, passed in two places:
ws://localhost:/?token=
AND in the controlRequest protobuf message:
controlRequest { token: "" }
Connecting without the token (or with a wrong one) results in controlResponse { success: false, reason: TOKEN_INVALID }.
The token is generated by the app when it displays the QR code for the mobile web remote. Previously-accepted tokens are cached in:
~/Library/Application Support/Insta360 Link Controller/startup.ini
under a [Token] section, with Unix timestamps as values (so you can pick the most recent):
[Token]
3cAKW91uM3JxnlDCOzY9b55PnYHlWh=1773071792
JUW5b7Nf7NjxR9jpfuJxAxs1HUwmXN=1773071791
Note: configparser lowercases keys by default — read the file manually to preserve case, as the token is case-sensitive.
The field assignments in DeviceInfoNotification are swapped compared to the proto:
So curDeviceSerialNum is now at field 2, and field 1 holds a single DeviceInfo message directly.
Within DeviceInfo, the ZoomInfo zoom message moved:
The ZoomInfo content itself is unchanged (curValue, minValue, maxValue).
Raw hex capture
Full deviceInfoNotify response frame from a live session (serial redacted):
080152b2020a9f020a0b4c696e6b2d58585858585858120e5858585858585858585858585858
180020002a070864106418900338ffffffffffffffffff014000480052e301080110012001
2801300048016032683270327832880101900100980100a00100a80101b001fc2ad8010090
0202980200a80201b00200c00200d00201d80201e00200e80200f00200f80200800301880300
900301980300a2030a686f72697a6f6e74616cad0300000000b00300b80300c00300ca032c
08ffffffffffffffffff0110ffffffffffffffffff0118ffffffffffffffffff0120ffffffff
ffffffffffffffffff01d2031608ffffffffffffffffff0110ffffffffffffffffff01da0313
0800120723303066663030184d2009280a3032e00300e80301f00300f8035a800400880401
120e5858585858585858585858585858
Tested on: Link Controller 2.2.1 (build 24), macOS 26.3.1, Insta360 Link (original).