Hard-won details, in case you extend this or port it to another model. All of it was measured, not read from documentation — LG publishes none.
- webOS 4+ (2018 and later) serves SSAP over TLS on port 3001 and resets connections to the old plaintext 3000. The client tries 3001 first and falls back.
- The TV presents a self-signed certificate for its own LAN address, so there is nothing to validate it against. Traffic is still encrypted.
- A silent socket close during registration — no error frame, just a close — is how some firmware rejects a manifest it dislikes. Code that treats a closed socket as a transport failure will report this misleadingly.
ssap://system/getSystemInfoanswers before registration, unlike almost everything else. That makes it the one useful diagnostic when pairing is broken. SSDP is unauthenticated and also answers when SSAP refuses.
The manifest is re-evaluated on every registration. Permissions are not frozen onto the client key at pairing time, so an existing key can present a different scope freely, with no re-pairing and no on-screen prompt.
Measured with one stored key against a 50UR8000PJB (fw 33.31.61):
| scope | getSystemSettings |
setSystemSettings |
|---|---|---|
minimal |
401 | 401 |
full |
OK | 401 |
signed |
OK | OK |
generic |
OK | 401 |
WRITE_SETTINGS and READ_UPDATE_INFO appear only inside the
signature-protected signed block of LG's manifest. The generic shape copies
that permission list verbatim but carries no signature, and is still refused —
so the signature is genuinely validated for those permissions, rather than the
block being read as a claim.
Unrecognised permission names are dropped silently rather than erroring, which is an easy way to end up with a narrower grant than you intended.
webOS 26 (LG firmware 43.00.92+) blacklisted the classic "LG Remote App"
identity — appId com.lge.test plus LG's baked-in signature. Pairing with it
still returns a client key, so it looks successful, but the TV then grants the
key almost nothing and answers 401 insufficient permissions (not registered).
On that firmware, presenting LG's signed manifest makes things worse.
The community fix is a signed block with a neutral appId and no signature at
all — the generic scope here. References:
- https://github.com/Orishas/webostv-webos26-fix
- home-assistant/core#172703
- JPersson77/LGTVCompanion#351
- Needs
{"category": X, "keys": [...]}with a non-emptykeysarray. An empty array returns500. - There is no enumeration.
getSystemSettingDescreturns500and wildcards are rejected. - One bad key poisons the whole batch, so keys must be probed one
(category, key)pair at a time. This is why probing is slow. 500means "no such key", not "forbidden". That asymmetry is what makes blind discovery possible at all: a returned value proves the key exists, and a500proves it does not. Do not report a 500 as a permission problem.- Object-valued keys (
eulaStatus,localeInfo,eulaInfoNetwork) are only readable with nocategoryfield at all. Sendingcategory: nullis a500.
- Writes go here. The
com.webos.settingsserviceandluna://variants return404— they are not bridged to the network. - Requires the
signedscope; see above. returnValue: truedoes not prove persistence. Always read back. This is not hypothetical — it is the single most important thing to know about this API, and the reason every write in this package is verified.- Object writes are read-modify-write. It is unknown whether the TV merges or replaces, so writing the full object means every other field survives either way. Check the field count afterwards: a shrunk object means the TV replaced rather than merged.
SSAP distinguishes these, and the distinction carries real information:
| Code | Meaning |
|---|---|
404 no such service or method |
The endpoint does not exist. |
401 insufficient permissions |
It exists, but this manifest cannot reach it. |
403 access denied |
Exists, refused. |
500 Application error |
Exists and ran; the arguments were wrong. |
A 401 therefore proves an endpoint exists, which is what makes blind
endpoint discovery possible.
Warning: there is no way to test existence without calling. A method that exists and tolerates an empty payload may actually run. Only probe verbs whose effect you are willing to cause.
26 exist on the test set: "" (none), 3d, aiPicture, aspectRatio,
caption, channel, commercial, commercialTv, general, hotelMode,
lgchannels, lock, musicCatch, network, option, other, picture,
push, sound, soundbar, support, time, timemachine, timer,
twinTv, voiceframework.
eulaStatus is reachable only with no category, and is absent from every
published category list — it was found by probing candidate key names harvested
from bscpylgtv's per-model settings dumps.
off is ssap://system/turnOff. on cannot use SSAP at all: a TV in standby
has no WebSocket listening, so the only way back up is a Wake-on-LAN magic
packet at the network layer, which needs Mobile TV On (Turn on via Wi-Fi
on some firmware) enabled on the TV.
The TV reports a MAC for every interface it has — wired, wifi and p2p — and does
not say which is active, so tv-privacy power on sends a packet to each. A packet
aimed at a down interface is simply ignored.